diff --git a/Cargo.lock b/Cargo.lock index 0bc53266deb..0822026fe5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/common/client-libs/validator-client/src/nyxd/contract_traits/mixnet_signing_client.rs b/common/client-libs/validator-client/src/nyxd/contract_traits/mixnet_signing_client.rs index 9b5761bed63..26a766a1908 100644 --- a/common/client-libs/validator-client/src/nyxd/contract_traits/mixnet_signing_client.rs +++ b/common/client-libs/validator-client/src/nyxd/contract_traits/mixnet_signing_client.rs @@ -683,6 +683,24 @@ pub trait MixnetSigningClient { .await } + async fn migrate_vested_mixnode(&self, fee: Option) -> Result { + self.execute_mixnet_contract(fee, MixnetExecuteMsg::MigrateVestedMixNode {}, vec![]) + .await + } + + async fn migrate_vested_delegation( + &self, + mix_id: MixId, + fee: Option, + ) -> Result { + self.execute_mixnet_contract( + fee, + MixnetExecuteMsg::MigrateVestedDelegation { mix_id }, + vec![], + ) + .await + } + #[cfg(feature = "contract-testing")] async fn testing_resolve_all_pending_events( &self, @@ -928,6 +946,12 @@ mod tests { MixnetExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, owner } => client .withdraw_delegator_reward_on_behalf(owner.parse().unwrap(), mix_id, None) .ignore(), + MixnetExecuteMsg::MigrateVestedMixNode { .. } => { + client.migrate_vested_mixnode(None).ignore() + } + MixnetExecuteMsg::MigrateVestedDelegation { mix_id } => { + client.migrate_vested_delegation(mix_id, None).ignore() + } #[cfg(feature = "contract-testing")] MixnetExecuteMsg::TestingResolveAllPendingEvents { .. } => { diff --git a/common/client-libs/validator-client/src/nyxd/contract_traits/vesting_signing_client.rs b/common/client-libs/validator-client/src/nyxd/contract_traits/vesting_signing_client.rs index 11c449fccd1..07472c4262b 100644 --- a/common/client-libs/validator-client/src/nyxd/contract_traits/vesting_signing_client.rs +++ b/common/client-libs/validator-client/src/nyxd/contract_traits/vesting_signing_client.rs @@ -437,6 +437,7 @@ where mod tests { use super::*; use crate::nyxd::contract_traits::tests::{mock_coin, IgnoreValue}; + use nym_vesting_contract_common::ExecuteMsg; // it's enough that this compiles and clippy is happy about it #[allow(dead_code)] @@ -560,6 +561,9 @@ mod tests { VestingExecuteMsg::UpdateLockedPledgeCap { address, cap } => client .update_locked_pledge_cap(address.parse().unwrap(), cap, None) .ignore(), + // those will never be manually called by clients + ExecuteMsg::TrackMigratedMixnode { .. } => "explicitly_ignored".ignore(), + ExecuteMsg::TrackMigratedDelegation { .. } => "explicitly_ignored".ignore(), }; } } diff --git a/common/commands/src/validator/mixnet/delegators/migrate_vested_delegation.rs b/common/commands/src/validator/mixnet/delegators/migrate_vested_delegation.rs new file mode 100644 index 00000000000..8523e63639d --- /dev/null +++ b/common/commands/src/validator/mixnet/delegators/migrate_vested_delegation.rs @@ -0,0 +1,42 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::context::SigningClient; +use clap::Parser; +use log::info; +use nym_mixnet_contract_common::MixId; +use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, MixnetSigningClient}; + +#[derive(Debug, Parser)] +pub struct Args { + #[clap(long)] + pub mix_id: Option, + + #[clap(long)] + pub identity_key: Option, +} + +pub async fn migrate_vested_delegation(args: Args, client: SigningClient) { + let mix_id = match args.mix_id { + Some(mix_id) => mix_id, + None => { + let identity_key = args + .identity_key + .expect("either mix_id or mix_identity has to be specified"); + let node_details = client + .get_mixnode_details_by_identity(identity_key) + .await + .expect("contract query failed") + .mixnode_details + .expect("mixnode with the specified identity doesnt exist"); + node_details.mix_id() + } + }; + + let res = client + .migrate_vested_delegation(mix_id, None) + .await + .expect("failed to migrate delegation!"); + + info!("migration result: {:?}", res) +} diff --git a/common/commands/src/validator/mixnet/delegators/mod.rs b/common/commands/src/validator/mixnet/delegators/mod.rs index f2cf156c0cb..17a5f9fa5c4 100644 --- a/common/commands/src/validator/mixnet/delegators/mod.rs +++ b/common/commands/src/validator/mixnet/delegators/mod.rs @@ -7,6 +7,7 @@ pub mod rewards; pub mod delegate_to_mixnode; pub mod delegate_to_multiple_mixnodes; +pub mod migrate_vested_delegation; pub mod query_for_delegations; pub mod undelegate_from_mixnode; pub mod vesting_delegate_to_mixnode; @@ -35,4 +36,6 @@ pub enum MixnetDelegatorsCommands { DelegateVesting(vesting_delegate_to_mixnode::Args), /// Undelegate from a mixnode (when originally using locked tokens) UndelegateVesting(vesting_undelegate_from_mixnode::Args), + /// Migrate the delegation to use liquid tokens + MigrateVestedDelegation(migrate_vested_delegation::Args), } diff --git a/common/commands/src/validator/mixnet/delegators/query_for_delegations.rs b/common/commands/src/validator/mixnet/delegators/query_for_delegations.rs index f29dae6db3b..b1a223b6033 100644 --- a/common/commands/src/validator/mixnet/delegators/query_for_delegations.rs +++ b/common/commands/src/validator/mixnet/delegators/query_for_delegations.rs @@ -96,6 +96,7 @@ async fn print_delegation_events(events: Vec, client: &Signin mix_id, amount, proxy, + .. } => { if owner.as_str() == client.nyxd.address().as_ref() { table.add_row(vec![ @@ -111,6 +112,7 @@ async fn print_delegation_events(events: Vec, client: &Signin owner, mix_id, proxy, + .. } => { if owner.as_str() == client.nyxd.address().as_ref() { table.add_row(vec![ diff --git a/common/commands/src/validator/mixnet/operators/gateway/gateway_bonding_sign_payload.rs b/common/commands/src/validator/mixnet/operators/gateway/gateway_bonding_sign_payload.rs index 3845a6c7efe..ba19f618674 100644 --- a/common/commands/src/validator/mixnet/operators/gateway/gateway_bonding_sign_payload.rs +++ b/common/commands/src/validator/mixnet/operators/gateway/gateway_bonding_sign_payload.rs @@ -8,7 +8,7 @@ use cosmwasm_std::Coin; use nym_bin_common::output_format::OutputFormat; use nym_mixnet_contract_common::construct_gateway_bonding_sign_payload; use nym_network_defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT}; -use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, NymContractsProvider}; +use nym_validator_client::nyxd::contract_traits::MixnetQueryClient; #[derive(Debug, Parser)] pub struct Args { @@ -39,10 +39,6 @@ pub struct Args { )] pub amount: u128, - /// Indicates whether the gateway is going to get bonded via a vesting account - #[arg(long)] - pub with_vesting_account: bool, - #[clap(short, long, default_value_t = OutputFormat::default())] output: OutputFormat, } @@ -74,15 +70,8 @@ pub async fn create_payload(args: Args, client: SigningClient) { }; let address = account_id_to_cw_addr(&client.address()); - let proxy = if args.with_vesting_account { - Some(account_id_to_cw_addr( - client.vesting_contract_address().unwrap(), - )) - } else { - None - }; - let payload = construct_gateway_bonding_sign_payload(nonce, address, proxy, coin, gateway); + let payload = construct_gateway_bonding_sign_payload(nonce, address, coin, gateway); let wrapper = DataWrapper::new(payload.to_base58_string().unwrap()); println!("{}", args.output.format(&wrapper)) } diff --git a/common/commands/src/validator/mixnet/operators/mixnode/families/create_family.rs b/common/commands/src/validator/mixnet/operators/mixnode/families/create_family.rs index 6acaca80d28..6e2c664a35b 100644 --- a/common/commands/src/validator/mixnet/operators/mixnode/families/create_family.rs +++ b/common/commands/src/validator/mixnet/operators/mixnode/families/create_family.rs @@ -5,33 +5,21 @@ use crate::context::SigningClient; use clap::Parser; use log::info; use nym_validator_client::nyxd::contract_traits::MixnetSigningClient; -use nym_validator_client::nyxd::contract_traits::VestingSigningClient; #[derive(Debug, Parser)] pub struct Args { /// Label that is going to be used for creating the family #[arg(long)] pub family_label: String, - - /// Indicates whether the family is going to get created via a vesting account - #[arg(long)] - pub with_vesting_account: bool, } pub async fn create_family(args: Args, client: SigningClient) { info!("Create family"); - let res = if args.with_vesting_account { - client - .vesting_create_family(args.family_label, None) - .await - .expect("failed to create family with vesting account") - } else { - client - .create_family(args.family_label, None) - .await - .expect("failed to create family") - }; + let res = client + .create_family(args.family_label, None) + .await + .expect("failed to create family"); info!("Family creation result: {:?}", res); } diff --git a/common/commands/src/validator/mixnet/operators/mixnode/families/create_family_join_permit_sign_payload.rs b/common/commands/src/validator/mixnet/operators/mixnode/families/create_family_join_permit_sign_payload.rs index c81dc35f690..ac397150ea7 100644 --- a/common/commands/src/validator/mixnet/operators/mixnode/families/create_family_join_permit_sign_payload.rs +++ b/common/commands/src/validator/mixnet/operators/mixnode/families/create_family_join_permit_sign_payload.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::context::QueryClient; -use crate::utils::{account_id_to_cw_addr, DataWrapper}; +use crate::utils::DataWrapper; use clap::Parser; use cosmrs::AccountId; use log::info; @@ -10,7 +10,7 @@ use nym_bin_common::output_format::OutputFormat; use nym_crypto::asymmetric::identity; use nym_mixnet_contract_common::construct_family_join_permit; use nym_mixnet_contract_common::families::FamilyHead; -use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, NymContractsProvider}; +use nym_validator_client::nyxd::contract_traits::MixnetQueryClient; #[derive(Debug, Parser)] pub struct Args { @@ -18,10 +18,6 @@ pub struct Args { #[arg(long)] pub address: AccountId, - /// Indicates whether the member joining the family is going to use the vesting account for joining. - #[arg(long)] - pub with_vesting_account: bool, - // might as well validate the value when parsing the arguments /// Identity of the member for whom we're issuing the permit #[arg(long)] @@ -68,18 +64,9 @@ pub async fn create_family_join_permit_sign_payload(args: Args, client: QueryCli } }; - // let address = account_id_to_cw_addr(&args.address); - let proxy = if args.with_vesting_account { - Some(account_id_to_cw_addr( - client.vesting_contract_address().unwrap(), - )) - } else { - None - }; - let head = FamilyHead::new(mixnode.bond_information.identity()); - let payload = construct_family_join_permit(nonce, head, proxy, args.member.to_base58_string()); + let payload = construct_family_join_permit(nonce, head, args.member.to_base58_string()); let wrapper = DataWrapper::new(payload.to_base58_string().unwrap()); println!("{}", args.output.format(&wrapper)) } diff --git a/common/commands/src/validator/mixnet/operators/mixnode/families/join_family.rs b/common/commands/src/validator/mixnet/operators/mixnode/families/join_family.rs index 411a8412b70..08b4c471c06 100644 --- a/common/commands/src/validator/mixnet/operators/mixnode/families/join_family.rs +++ b/common/commands/src/validator/mixnet/operators/mixnode/families/join_family.rs @@ -8,7 +8,6 @@ use nym_contracts_common::signing::MessageSignature; use nym_crypto::asymmetric::identity; use nym_mixnet_contract_common::families::FamilyHead; use nym_validator_client::nyxd::contract_traits::MixnetSigningClient; -use nym_validator_client::nyxd::contract_traits::VestingSigningClient; #[derive(Debug, Parser)] pub struct Args { @@ -16,10 +15,6 @@ pub struct Args { #[arg(long)] pub family_head: identity::PublicKey, - /// Indicates whether the member joining the family is going to do so via the vesting contract - #[arg(long)] - pub with_vesting_account: bool, - /// Permission, as provided by the family head, for joining the family #[arg(long)] pub join_permit: MessageSignature, @@ -30,17 +25,10 @@ pub async fn join_family(args: Args, client: SigningClient) { let family_head = FamilyHead::new(args.family_head.to_base58_string()); - let res = if args.with_vesting_account { - client - .vesting_join_family(args.join_permit, family_head, None) - .await - .expect("failed to join family with vesting account") - } else { - client - .join_family(args.join_permit, family_head, None) - .await - .expect("failed to join family") - }; + let res = client + .join_family(args.join_permit, family_head, None) + .await + .expect("failed to join family"); info!("Family join result: {:?}", res); } diff --git a/common/commands/src/validator/mixnet/operators/mixnode/families/leave_family.rs b/common/commands/src/validator/mixnet/operators/mixnode/families/leave_family.rs index 0673be508ae..d9c31e39330 100644 --- a/common/commands/src/validator/mixnet/operators/mixnode/families/leave_family.rs +++ b/common/commands/src/validator/mixnet/operators/mixnode/families/leave_family.rs @@ -7,17 +7,12 @@ use log::info; use nym_crypto::asymmetric::identity; use nym_mixnet_contract_common::families::FamilyHead; use nym_validator_client::nyxd::contract_traits::MixnetSigningClient; -use nym_validator_client::nyxd::contract_traits::VestingSigningClient; #[derive(Debug, Parser)] pub struct Args { /// The head of the family that we intend to leave #[arg(long)] pub family_head: identity::PublicKey, - - /// Indicates whether we joined the family via the vesting contract - #[arg(long)] - pub with_vesting_account: bool, } pub async fn leave_family(args: Args, client: SigningClient) { @@ -25,17 +20,10 @@ pub async fn leave_family(args: Args, client: SigningClient) { let family_head = FamilyHead::new(args.family_head.to_base58_string()); - let res = if args.with_vesting_account { - client - .vesting_leave_family(family_head, None) - .await - .expect("failed to leave family with vesting account") - } else { - client - .leave_family(family_head, None) - .await - .expect("failed to leave family") - }; + let res = client + .leave_family(family_head, None) + .await + .expect("failed to leave family"); info!("Family leave result: {:?}", res); } diff --git a/common/commands/src/validator/mixnet/operators/mixnode/migrate_vested_mixnode.rs b/common/commands/src/validator/mixnet/operators/mixnode/migrate_vested_mixnode.rs new file mode 100644 index 00000000000..95bd9d0573c --- /dev/null +++ b/common/commands/src/validator/mixnet/operators/mixnode/migrate_vested_mixnode.rs @@ -0,0 +1,19 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::context::SigningClient; +use clap::Parser; +use log::info; +use nym_validator_client::nyxd::contract_traits::MixnetSigningClient; + +#[derive(Debug, Parser)] +pub struct Args {} + +pub async fn migrate_vested_mixnode(_args: Args, client: SigningClient) { + let res = client + .migrate_vested_mixnode(None) + .await + .expect("failed to migrate mixnode!"); + + info!("migration result: {:?}", res) +} diff --git a/common/commands/src/validator/mixnet/operators/mixnode/mixnode_bonding_sign_payload.rs b/common/commands/src/validator/mixnet/operators/mixnode/mixnode_bonding_sign_payload.rs index 332de7614eb..a492b3a0b6d 100644 --- a/common/commands/src/validator/mixnet/operators/mixnode/mixnode_bonding_sign_payload.rs +++ b/common/commands/src/validator/mixnet/operators/mixnode/mixnode_bonding_sign_payload.rs @@ -11,7 +11,7 @@ use nym_mixnet_contract_common::{construct_mixnode_bonding_sign_payload, MixNode use nym_network_defaults::{ DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT, }; -use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, NymContractsProvider}; +use nym_validator_client::nyxd::contract_traits::MixnetQueryClient; use nym_validator_client::nyxd::CosmWasmCoin; #[derive(Debug, Parser)] @@ -52,10 +52,6 @@ pub struct Args { )] pub amount: u128, - /// Indicates whether the mixnode is going to get bonded via a vesting account - #[arg(long)] - pub with_vesting_account: bool, - #[clap(short, long, default_value_t = OutputFormat::default())] output: OutputFormat, } @@ -100,16 +96,9 @@ pub async fn create_payload(args: Args, client: SigningClient) { }; let address = account_id_to_cw_addr(&client.address()); - let proxy = if args.with_vesting_account { - Some(account_id_to_cw_addr( - client.vesting_contract_address().unwrap(), - )) - } else { - None - }; let payload = - construct_mixnode_bonding_sign_payload(nonce, address, proxy, coin, mixnode, cost_params); + construct_mixnode_bonding_sign_payload(nonce, address, coin, mixnode, cost_params); let wrapper = DataWrapper::new(payload.to_base58_string().unwrap()); println!("{}", args.output.format(&wrapper)) } diff --git a/common/commands/src/validator/mixnet/operators/mixnode/mod.rs b/common/commands/src/validator/mixnet/operators/mixnode/mod.rs index 6e5283d77e1..abb5060e9b2 100644 --- a/common/commands/src/validator/mixnet/operators/mixnode/mod.rs +++ b/common/commands/src/validator/mixnet/operators/mixnode/mod.rs @@ -7,6 +7,7 @@ pub mod bond_mixnode; pub mod decrease_pledge; pub mod families; pub mod keys; +pub mod migrate_vested_mixnode; pub mod mixnode_bonding_sign_payload; pub mod pledge_more; pub mod rewards; @@ -52,4 +53,6 @@ pub enum MixnetOperatorsMixnodeCommands { DecreasePledge(decrease_pledge::Args), /// Decrease pledge with locked tokens DecreasePledgeVesting(vesting_decrease_pledge::Args), + /// Migrate the mixnode to use liquid tokens + MigrateVestedNode(migrate_vested_mixnode::Args), } diff --git a/common/cosmwasm-smart-contracts/contracts-common/src/signing/mod.rs b/common/cosmwasm-smart-contracts/contracts-common/src/signing/mod.rs index 23018c92492..d2722b2f597 100644 --- a/common/cosmwasm-smart-contracts/contracts-common/src/signing/mod.rs +++ b/common/cosmwasm-smart-contracts/contracts-common/src/signing/mod.rs @@ -218,7 +218,6 @@ where #[derive(Serialize)] pub struct ContractMessageContent { pub sender: Addr, - pub proxy: Option, pub funds: Vec, pub data: T, } @@ -233,25 +232,17 @@ where } impl ContractMessageContent { - pub fn new(sender: Addr, proxy: Option, funds: Vec, data: T) -> Self { + pub fn new(sender: Addr, funds: Vec, data: T) -> Self { ContractMessageContent { sender, - proxy, funds, data, } } pub fn new_with_info(info: MessageInfo, signer: Addr, data: T) -> Self { - let proxy = if info.sender == signer { - None - } else { - Some(info.sender) - }; - ContractMessageContent { sender: signer, - proxy, funds: info.funds, data, } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs index 3a7cdd77025..f30dc63a73f 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs @@ -65,7 +65,6 @@ impl Delegation { cumulative_reward_ratio: Decimal, amount: Coin, height: u64, - proxy: Option, ) -> Self { assert!( amount.amount <= TOKEN_SUPPLY, @@ -78,7 +77,7 @@ impl Delegation { cumulative_reward_ratio, amount, height, - proxy, + proxy: None, } } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs index ec6e79c65f5..39dab44263b 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs @@ -76,21 +76,11 @@ pub enum MixnetContractError { #[error("Received multiple coin types during staking")] MultipleDenoms, - #[error("Proxy address mismatch, expected {existing}, got {incoming}")] - ProxyMismatch { existing: String, incoming: String }, - #[error("Proxy address ({received}) is not set to the vesting contract ({vesting_contract})")] ProxyIsNotVestingContract { received: Addr, vesting_contract: Addr, }, - #[error( - "Sender of this message ({received}) is not the vesting contract ({vesting_contract})" - )] - SenderIsNotVestingContract { - received: Addr, - vesting_contract: Addr, - }, #[error("Failed to recover ed25519 public key from its base58 representation - {0}")] MalformedEd25519IdentityKey(String), @@ -239,6 +229,17 @@ pub enum MixnetContractError { #[from] source: ApiVerifierError, }, + + #[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 { diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs index 365adf9df3f..7ebb604225f 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs @@ -103,7 +103,6 @@ impl Display for MixnetEventType { // attributes that are used in multiple places pub const OWNER_KEY: &str = "owner"; pub const AMOUNT_KEY: &str = "amount"; -pub const PROXY_KEY: &str = "proxy"; // event-specific attributes @@ -163,7 +162,6 @@ pub const NEW_EPOCHS_IN_INTERVAL: &str = "new_epochs_in_interval"; pub fn new_delegation_event( created_at: BlockHeight, delegator: &Addr, - proxy: &Option, amount: &Coin, mix_id: MixId, unit_reward: Decimal, @@ -171,58 +169,34 @@ pub fn new_delegation_event( Event::new(MixnetEventType::Delegation) .add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string()) .add_attribute(DELEGATOR_KEY, delegator) - .add_optional_attribute(PROXY_KEY, proxy.as_ref()) .add_attribute(AMOUNT_KEY, amount.to_string()) .add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string()) .add_attribute(UNIT_REWARD_KEY, unit_reward.to_string()) } -pub fn new_delegation_on_unbonded_node_event( - delegator: &Addr, - proxy: &Option, - mix_id: MixId, -) -> Event { +pub fn new_delegation_on_unbonded_node_event(delegator: &Addr, mix_id: MixId) -> Event { Event::new(MixnetEventType::Delegation) .add_attribute(DELEGATOR_KEY, delegator) - .add_optional_attribute(PROXY_KEY, proxy.as_ref()) .add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string()) } -pub fn new_pending_delegation_event( - delegator: &Addr, - proxy: &Option, - amount: &Coin, - mix_id: MixId, -) -> Event { +pub fn new_pending_delegation_event(delegator: &Addr, amount: &Coin, mix_id: MixId) -> Event { Event::new(MixnetEventType::PendingDelegation) .add_attribute(DELEGATOR_KEY, delegator) - .add_optional_attribute(PROXY_KEY, proxy.as_ref()) .add_attribute(AMOUNT_KEY, amount.to_string()) .add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string()) } -pub fn new_withdraw_operator_reward_event( - owner: &Addr, - proxy: &Option, - amount: Coin, - mix_id: MixId, -) -> Event { +pub fn new_withdraw_operator_reward_event(owner: &Addr, amount: Coin, mix_id: MixId) -> Event { Event::new(MixnetEventType::WithdrawOperatorReward) .add_attribute(OWNER_KEY, owner.as_str()) - .add_optional_attribute(PROXY_KEY, proxy.as_ref()) .add_attribute(AMOUNT_KEY, amount.to_string()) .add_attribute(MIX_ID_KEY, mix_id.to_string()) } -pub fn new_withdraw_delegator_reward_event( - delegator: &Addr, - proxy: &Option, - amount: Coin, - mix_id: MixId, -) -> Event { +pub fn new_withdraw_delegator_reward_event(delegator: &Addr, amount: Coin, mix_id: MixId) -> Event { Event::new(MixnetEventType::WithdrawDelegatorReward) .add_attribute(DELEGATOR_KEY, delegator) - .add_optional_attribute(PROXY_KEY, proxy.as_ref()) .add_attribute(AMOUNT_KEY, amount.to_string()) .add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string()) } @@ -278,59 +252,43 @@ pub fn new_pending_rewarding_params_update_event( ) } -pub fn new_undelegation_event( - created_at: BlockHeight, - delegator: &Addr, - proxy: &Option, - mix_id: MixId, -) -> Event { +pub fn new_undelegation_event(created_at: BlockHeight, delegator: &Addr, mix_id: MixId) -> Event { Event::new(MixnetEventType::Undelegation) .add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string()) .add_attribute(DELEGATOR_KEY, delegator) - .add_optional_attribute(PROXY_KEY, proxy.as_ref()) .add_attribute(MIX_ID_KEY, mix_id.to_string()) } -pub fn new_pending_undelegation_event( - delegator: &Addr, - proxy: &Option, - mix_id: MixId, -) -> Event { +pub fn new_pending_undelegation_event(delegator: &Addr, mix_id: MixId) -> Event { Event::new(MixnetEventType::PendingUndelegation) .add_attribute(DELEGATOR_KEY, delegator) - .add_optional_attribute(PROXY_KEY, proxy.as_ref()) .add_attribute(MIX_ID_KEY, mix_id.to_string()) } pub fn new_gateway_bonding_event( owner: &Addr, - proxy: &Option, amount: &Coin, identity: IdentityKeyRef<'_>, ) -> Event { Event::new(MixnetEventType::GatewayBonding) .add_attribute(OWNER_KEY, owner) .add_attribute(NODE_IDENTITY_KEY, identity) - .add_optional_attribute(PROXY_KEY, proxy.as_ref()) .add_attribute(AMOUNT_KEY, amount.to_string()) } pub fn new_gateway_unbonding_event( owner: &Addr, - proxy: &Option, amount: &Coin, identity: IdentityKeyRef<'_>, ) -> Event { Event::new(MixnetEventType::GatewayUnbonding) .add_attribute(OWNER_KEY, owner) .add_attribute(NODE_IDENTITY_KEY, identity) - .add_optional_attribute(PROXY_KEY, proxy.as_ref()) .add_attribute(AMOUNT_KEY, amount.to_string()) } pub fn new_mixnode_bonding_event( owner: &Addr, - proxy: &Option, amount: &Coin, identity: IdentityKeyRef<'_>, mix_id: MixId, @@ -341,7 +299,6 @@ pub fn new_mixnode_bonding_event( .add_attribute(MIX_ID_KEY, mix_id.to_string()) .add_attribute(NODE_IDENTITY_KEY, identity) .add_attribute(OWNER_KEY, owner) - .add_optional_attribute(PROXY_KEY, proxy.as_ref()) .add_attribute(ASSIGNED_LAYER_KEY, assigned_layer) .add_attribute(AMOUNT_KEY, amount.to_string()) } @@ -380,7 +337,6 @@ pub fn new_mixnode_unbonding_event(created_at: BlockHeight, mix_id: MixId) -> Ev pub fn new_pending_mixnode_unbonding_event( owner: &Addr, - proxy: &Option, identity: IdentityKeyRef<'_>, mix_id: MixId, ) -> Event { @@ -388,43 +344,33 @@ pub fn new_pending_mixnode_unbonding_event( .add_attribute(MIX_ID_KEY, mix_id.to_string()) .add_attribute(NODE_IDENTITY_KEY, identity) .add_attribute(OWNER_KEY, owner) - .add_optional_attribute(PROXY_KEY, proxy.as_ref()) } pub fn new_mixnode_config_update_event( mix_id: MixId, owner: &Addr, - proxy: &Option, update: &MixNodeConfigUpdate, ) -> Event { Event::new(MixnetEventType::MixnodeConfigUpdate) .add_attribute(MIX_ID_KEY, mix_id.to_string()) .add_attribute(OWNER_KEY, owner) - .add_optional_attribute(PROXY_KEY, proxy.as_ref()) .add_attribute(UPDATED_MIXNODE_CONFIG_KEY, update.to_inline_json()) } -pub fn new_gateway_config_update_event( - owner: &Addr, - proxy: &Option, - update: &GatewayConfigUpdate, -) -> Event { +pub fn new_gateway_config_update_event(owner: &Addr, update: &GatewayConfigUpdate) -> Event { Event::new(MixnetEventType::GatewayConfigUpdate) .add_attribute(OWNER_KEY, owner) - .add_optional_attribute(PROXY_KEY, proxy.as_ref()) .add_attribute(UPDATED_GATEWAY_CONFIG_KEY, update.to_inline_json()) } pub fn new_mixnode_pending_cost_params_update_event( mix_id: MixId, owner: &Addr, - proxy: &Option, new_costs: &MixNodeCostParams, ) -> Event { Event::new(MixnetEventType::PendingMixnodeCostParamsUpdate) .add_attribute(MIX_ID_KEY, mix_id.to_string()) .add_attribute(OWNER_KEY, owner) - .add_optional_attribute(PROXY_KEY, proxy.as_ref()) .add_attribute(UPDATED_MIXNODE_COST_PARAMS_KEY, new_costs.to_inline_json()) } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/families.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/families.rs index 5b44483c830..58f74f4e087 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/families.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/families.rs @@ -3,7 +3,6 @@ use crate::{IdentityKey, IdentityKeyRef}; use cosmwasm_schema::cw_serde; -use cosmwasm_std::Addr; use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::fmt::{Display, Formatter}; @@ -84,10 +83,10 @@ impl FamilyHead { } impl Family { - pub fn new(head: FamilyHead, proxy: Option, label: String) -> Self { + pub fn new(head: FamilyHead, label: String) -> Self { Family { head, - proxy: proxy.map(|p| p.to_string()), + proxy: None, label, } } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/gateway.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/gateway.rs index fd29090374c..ff991853fb5 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/gateway.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/gateway.rs @@ -55,19 +55,13 @@ pub struct GatewayBond { } impl GatewayBond { - pub fn new( - pledge_amount: Coin, - owner: Addr, - block_height: u64, - gateway: Gateway, - proxy: Option, - ) -> Self { + pub fn new(pledge_amount: Coin, owner: Addr, block_height: u64, gateway: Gateway) -> Self { GatewayBond { pledge_amount, owner, block_height, gateway, - proxy, + proxy: None, } } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs index a09d21dd2d2..c3a4415ceda 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs @@ -518,7 +518,6 @@ impl MixNodeBond { original_pledge: Coin, layer: Layer, mix_node: MixNode, - proxy: Option, bonding_height: u64, ) -> Self { MixNodeBond { @@ -527,7 +526,7 @@ impl MixNodeBond { original_pledge, layer, mix_node, - proxy, + proxy: None, bonding_height, is_unbonding: false, } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs index ca154e231b6..bb250d78200 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs @@ -269,6 +269,12 @@ pub enum ExecuteMsg { owner: String, }, + // vesting migration: + MigrateVestedMixNode {}, + MigrateVestedDelegation { + 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::MigrateVestedDelegation { .. } => "migrate vested delegation".to_string(), + #[cfg(feature = "contract-testing")] ExecuteMsg::TestingResolveAllPendingEvents { .. } => { "resolving all pending events".into() diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/pending_events.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/pending_events.rs index 8a60af94a48..8d97e204944 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/pending_events.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/pending_events.rs @@ -38,6 +38,7 @@ pub enum PendingEpochEventKind { /// Request to create a delegation towards particular mixnode. /// Note that if such delegation already exists, it will get updated with the provided token amount. #[serde(alias = "Delegate")] + #[non_exhaustive] Delegate { /// The address of the owner of the delegation. owner: Addr, @@ -55,6 +56,7 @@ pub enum PendingEpochEventKind { /// Request to remove delegation from particular mixnode. #[serde(alias = "Undelegate")] + #[non_exhaustive] Undelegate { /// The address of the owner of the delegation. owner: Addr, @@ -109,6 +111,23 @@ impl PendingEpochEventKind { kind: self, } } + + pub fn new_delegate(owner: Addr, mix_id: MixId, amount: Coin) -> Self { + PendingEpochEventKind::Delegate { + owner, + mix_id, + amount, + proxy: None, + } + } + + pub fn new_undelegate(owner: Addr, mix_id: MixId) -> Self { + PendingEpochEventKind::Undelegate { + owner, + mix_id, + proxy: None, + } + } } impl From<(EpochEventId, PendingEpochEventData)> for PendingEpochEvent { diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/simulator/simulated_node.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/simulator/simulated_node.rs index 5af272366e6..12a9e967bd1 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/simulator/simulated_node.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/rewarding/simulator/simulated_node.rs @@ -47,7 +47,6 @@ impl SimulatedNode { self.rewarding_details.total_unit_reward, delegation, 42, - None, ); self.delegations.insert(delegator, delegation); diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/signing_types.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/signing_types.rs index 57ad49dc96b..84c9b8b4aaf 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/signing_types.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/signing_types.rs @@ -37,13 +37,12 @@ impl SigningPurpose for MixnodeBondingPayload { pub fn construct_mixnode_bonding_sign_payload( nonce: Nonce, sender: Addr, - proxy: Option, pledge: Coin, mix_node: MixNode, cost_params: MixNodeCostParams, ) -> SignableMixNodeBondingMsg { let payload = MixnodeBondingPayload::new(mix_node, cost_params); - let content = ContractMessageContent::new(sender, proxy, vec![pledge], payload); + let content = ContractMessageContent::new(sender, vec![pledge], payload); SignableMessage::new(nonce, content) } @@ -68,12 +67,11 @@ impl SigningPurpose for GatewayBondingPayload { pub fn construct_gateway_bonding_sign_payload( nonce: Nonce, sender: Addr, - proxy: Option, pledge: Coin, gateway: Gateway, ) -> SignableGatewayBondingMsg { let payload = GatewayBondingPayload::new(gateway); - let content = ContractMessageContent::new(sender, proxy, vec![pledge], payload); + let content = ContractMessageContent::new(sender, vec![pledge], payload); SignableMessage::new(nonce, content) } @@ -82,17 +80,14 @@ pub fn construct_gateway_bonding_sign_payload( pub struct FamilyJoinPermit { // the granter of this permit family_head: FamilyHead, - // whether the **member** will want to join via the proxy (i.e. vesting contract) - proxy: Option, // the actual member we want to permit to join member_node: IdentityKey, } impl FamilyJoinPermit { - pub fn new(family_head: FamilyHead, proxy: Option, member_node: IdentityKey) -> Self { + pub fn new(family_head: FamilyHead, member_node: IdentityKey) -> Self { Self { family_head, - proxy, member_node, } } @@ -107,10 +102,9 @@ impl SigningPurpose for FamilyJoinPermit { pub fn construct_family_join_permit( nonce: Nonce, family_head: FamilyHead, - proxy: Option, member_node: IdentityKey, ) -> SignableFamilyJoinPermitMsg { - let payload = FamilyJoinPermit::new(family_head, proxy, member_node); + let payload = FamilyJoinPermit::new(family_head, member_node); // note: we're NOT wrapping it in `ContractMessageContent` because the family head is not going to be the one // sending the message to the contract diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/events.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/events.rs index d7dc757956d..b957adbe10c 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/events.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/events.rs @@ -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") +} diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs index 6593926445f..eefe07e9f15 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs @@ -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" + } } } } diff --git a/common/types/src/pending_events.rs b/common/types/src/pending_events.rs index f5789890bf8..d36e0b978cd 100644 --- a/common/types/src/pending_events.rs +++ b/common/types/src/pending_events.rs @@ -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, diff --git a/contracts/mixnet-vesting-integration-tests/src/decrease_mixnode_pledge.rs b/contracts/mixnet-vesting-integration-tests/src/decrease_mixnode_pledge.rs deleted file mode 100644 index a9930e23f79..00000000000 --- a/contracts/mixnet-vesting-integration-tests/src/decrease_mixnode_pledge.rs +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::support::helpers::{mix_coin, mix_coins, vesting_owner}; -use crate::support::setup::{TestSetup, MIX_DENOM}; -use cosmwasm_std::Addr; -use cw_multi_test::Executor; -use nym_contracts_common::Percent; -use nym_mixnet_contract_common::error::MixnetContractError; -use nym_mixnet_contract_common::{ContractStateParams, MixNodeCostParams}; -use nym_mixnet_contract_common::{MixOwnershipResponse, QueryMsg as MixnetQueryMsg}; -use nym_vesting_contract_common::{ExecuteMsg as VestingExecuteMsg, VestingContractError}; - -#[test] -fn decrease_mixnode_pledge_from_vesting_account_with_minimum_pledge() { - let mut test = TestSetup::new_simple(); - let vesting_account = "vesting-account"; - - // 0. get the minimum pledge amount - let state_params: ContractStateParams = test - .app - .wrap() - .query_wasm_smart(test.mixnet_contract(), &MixnetQueryMsg::GetStateParams {}) - .unwrap(); - let minimum_pledge = state_params.minimum_mixnode_pledge; - - // 1. create vesting account - let create_msg = VestingExecuteMsg::CreateAccount { - owner_address: vesting_account.to_string(), - staking_address: None, - vesting_spec: None, - cap: None, - }; - - test.app - .execute_contract( - vesting_owner(), - test.vesting_contract(), - &create_msg, - &mix_coins(1_000_000_000), - ) - .unwrap(); - - // 2. bond mixnode with the vesting account - let pledge = minimum_pledge.clone(); - - let cost_params = MixNodeCostParams { - profit_margin_percent: Percent::from_percentage_value(10).unwrap(), - interval_operating_cost: mix_coin(40_000_000), - }; - - let (mix_node, owner_signature) = test.valid_mixnode_with_sig( - vesting_account, - Some(test.vesting_contract()), - cost_params.clone(), - pledge.clone(), - ); - - let bond_msg = VestingExecuteMsg::BondMixnode { - mix_node, - cost_params, - owner_signature, - amount: pledge.clone(), - }; - test.app - .execute_contract( - Addr::unchecked(vesting_account), - test.vesting_contract(), - &bond_msg, - &[], - ) - .unwrap(); - - // 3. try to decrease the pledge - - // trying to decrease by a zero amount - not valid - let decrease_pledge_msg = VestingExecuteMsg::DecreasePledge { - amount: mix_coin(0), - }; - let res_zero = test - .app - .execute_contract( - Addr::unchecked(vesting_account), - test.vesting_contract(), - &decrease_pledge_msg, - &[], - ) - .unwrap_err(); - - assert_eq!( - VestingContractError::EmptyFunds, - res_zero.downcast().unwrap() - ); - - // trying to go below the cap - also not valid - let amount = mix_coin(50_000); - let decrease_pledge_msg = VestingExecuteMsg::DecreasePledge { - amount: amount.clone(), - }; - let res_below = test - .app - .execute_contract( - Addr::unchecked(vesting_account), - test.vesting_contract(), - &decrease_pledge_msg, - &[], - ) - .unwrap_err(); - assert_eq!( - MixnetContractError::InvalidPledgeReduction { - current: pledge.amount, - decrease_by: amount.amount, - minimum: minimum_pledge.amount, - denom: minimum_pledge.denom - }, - res_below.downcast().unwrap() - ) -} - -#[test] -fn decrease_mixnode_pledge_from_vesting_account_with_sufficient_pledge() { - let mut test = TestSetup::new_simple(); - let vesting_account = "vesting-account"; - - // 1. create vesting account - let create_msg = VestingExecuteMsg::CreateAccount { - owner_address: vesting_account.to_string(), - staking_address: None, - vesting_spec: None, - cap: None, - }; - - test.app - .execute_contract( - vesting_owner(), - test.vesting_contract(), - &create_msg, - &mix_coins(10_000_000_000), - ) - .unwrap(); - - // 2. bond mixnode with the vesting account - let pledge = mix_coin(150_000_000); - - let cost_params = MixNodeCostParams { - profit_margin_percent: Percent::from_percentage_value(10).unwrap(), - interval_operating_cost: mix_coin(40_000_000), - }; - - let (mix_node, owner_signature) = test.valid_mixnode_with_sig( - vesting_account, - Some(test.vesting_contract()), - cost_params.clone(), - pledge.clone(), - ); - - let bond_msg = VestingExecuteMsg::BondMixnode { - mix_node, - cost_params, - owner_signature, - amount: pledge, - }; - test.app - .execute_contract( - Addr::unchecked(vesting_account), - test.vesting_contract(), - &bond_msg, - &[], - ) - .unwrap(); - - // 3. try to decrease the pledge - let before: MixOwnershipResponse = test - .app - .wrap() - .query_wasm_smart( - test.mixnet_contract(), - &MixnetQueryMsg::GetOwnedMixnode { - address: vesting_account.to_string(), - }, - ) - .unwrap(); - let balance_before = test - .app - .wrap() - .query_balance(test.vesting_contract(), MIX_DENOM) - .unwrap(); - assert_eq!(balance_before.amount.u128(), 9_850_000_000); - - let decrease_pledge_msg = VestingExecuteMsg::DecreasePledge { - amount: mix_coin(50_000_000), - }; - test.app - .execute_contract( - Addr::unchecked(vesting_account), - test.vesting_contract(), - &decrease_pledge_msg, - &[], - ) - .unwrap(); - - let after_decrease: MixOwnershipResponse = test - .app - .wrap() - .query_wasm_smart( - test.mixnet_contract(), - &MixnetQueryMsg::GetOwnedMixnode { - address: vesting_account.to_string(), - }, - ) - .unwrap(); - - // note: nothing has changed with the pledge because the event hasn't been resolved yet! - assert_eq!(before.address, after_decrease.address); - let before_details = before.mixnode_details.unwrap(); - let after_details = after_decrease.mixnode_details.unwrap(); - assert_eq!( - before_details.rewarding_details, - after_details.rewarding_details - ); - assert_eq!( - before_details.bond_information, - after_details.bond_information - ); - - // but we have the pending change saved now! - assert!(before_details.pending_changes.pledge_change.is_none()); - assert_eq!(Some(1), after_details.pending_changes.pledge_change); - - // 4. resolve events - test.advance_mixnet_epoch(); - - let balance_after = test - .app - .wrap() - .query_balance(test.vesting_contract(), MIX_DENOM) - .unwrap(); - assert_eq!(balance_after.amount.u128(), 9_900_000_000); -} diff --git a/contracts/mixnet-vesting-integration-tests/src/support/helpers.rs b/contracts/mixnet-vesting-integration-tests/src/support/helpers.rs index f739d8556a0..a714ccf493a 100644 --- a/contracts/mixnet-vesting-integration-tests/src/support/helpers.rs +++ b/contracts/mixnet-vesting-integration-tests/src/support/helpers.rs @@ -12,27 +12,33 @@ pub fn mixnet_owner() -> Addr { Addr::unchecked(MIXNET_OWNER) } +#[allow(unused)] pub fn vesting_owner() -> Addr { Addr::unchecked(VESTING_OWNER) } +#[allow(unused)] pub fn rewarding_validator() -> Addr { Addr::unchecked(REWARDING_VALIDATOR) } +#[allow(unused)] pub fn mix_coins(amount: u128) -> Vec { coins(amount, MIX_DENOM) } +#[allow(unused)] pub fn mix_coin(amount: u128) -> Coin { coin(amount, MIX_DENOM) } +#[allow(unused)] pub fn test_rng() -> ChaCha20Rng { let dummy_seed = [42u8; 32]; ChaCha20Rng::from_seed(dummy_seed) } +#[allow(unused)] pub fn mixnet_contract_wrapper() -> Box> { Box::new( ContractWrapper::new( diff --git a/contracts/mixnet-vesting-integration-tests/src/support/setup.rs b/contracts/mixnet-vesting-integration-tests/src/support/setup.rs index 4f1a4d3bec7..4ef1b04cbac 100644 --- a/contracts/mixnet-vesting-integration-tests/src/support/setup.rs +++ b/contracts/mixnet-vesting-integration-tests/src/support/setup.rs @@ -26,6 +26,7 @@ pub const VESTING_OWNER: &str = "vesting-owner"; pub const REWARDING_VALIDATOR: &str = "rewarding-validator"; pub const MIX_DENOM: &str = "unym"; +#[allow(unused)] pub struct ContractInstantiationResult { mixnet_contract_address: Addr, vesting_contract_address: Addr, @@ -69,14 +70,15 @@ impl TestSetupBuilder { } } +#[allow(unused)] pub struct TestSetup { pub app: App, pub rng: ChaCha20Rng, pub mixnet_contract: Addr, - pub vesting_contract: Addr, } +#[allow(unused)] impl TestSetup { pub fn new_simple() -> Self { TestSetup::new(Default::default(), fixtures::default_mixnet_init_msg()) @@ -91,7 +93,6 @@ impl TestSetup { app, rng: test_rng(), mixnet_contract: contracts.mixnet_contract_address, - vesting_contract: contracts.vesting_contract_address, } } @@ -99,10 +100,6 @@ impl TestSetup { self.mixnet_contract.clone() } - pub fn vesting_contract(&self) -> Addr { - self.vesting_contract.clone() - } - pub fn skip_to_current_epoch_end(&mut self) { let current_interval: CurrentIntervalResponse = self .app @@ -209,7 +206,6 @@ impl TestSetup { pub fn valid_mixnode_with_sig( &mut self, owner: &str, - proxy: Option, cost_params: MixNodeCostParams, stake: Coin, ) -> (MixNode, MessageSignature) { @@ -239,8 +235,7 @@ impl TestSetup { }; let payload = MixnodeBondingPayload::new(mixnode.clone(), cost_params); - let content = - ContractMessageContent::new(Addr::unchecked(owner), proxy, vec![stake], payload); + let content = ContractMessageContent::new(Addr::unchecked(owner), vec![stake], payload); let sign_payload = SignableMixNodeBondingMsg::new(signing_nonce, content); let plaintext = sign_payload.to_plaintext().unwrap(); let signature = keypair.private_key().sign(plaintext); diff --git a/contracts/mixnet-vesting-integration-tests/src/tests.rs b/contracts/mixnet-vesting-integration-tests/src/tests.rs index d560e38394b..826adcfd53c 100644 --- a/contracts/mixnet-vesting-integration-tests/src/tests.rs +++ b/contracts/mixnet-vesting-integration-tests/src/tests.rs @@ -1,5 +1,4 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -mod decrease_mixnode_pledge; mod support; diff --git a/contracts/mixnet/schema/nym-mixnet-contract.json b/contracts/mixnet/schema/nym-mixnet-contract.json index 971cbc27fb8..7ab9829107a 100644 --- a/contracts/mixnet/schema/nym-mixnet-contract.json +++ b/contracts/mixnet/schema/nym-mixnet-contract.json @@ -1146,6 +1146,42 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "migrate_vested_mix_node" + ], + "properties": { + "migrate_vested_mix_node": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "migrate_vested_delegation" + ], + "properties": { + "migrate_vested_delegation": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { diff --git a/contracts/mixnet/schema/raw/execute.json b/contracts/mixnet/schema/raw/execute.json index c4f1817fc9b..8174fd3d1db 100644 --- a/contracts/mixnet/schema/raw/execute.json +++ b/contracts/mixnet/schema/raw/execute.json @@ -1029,6 +1029,42 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "migrate_vested_mix_node" + ], + "properties": { + "migrate_vested_mix_node": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "migrate_vested_delegation" + ], + "properties": { + "migrate_vested_delegation": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index e6c755d05d6..cd58e059143 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -118,44 +118,6 @@ pub fn execute( ExecuteMsg::KickFamilyMember { member } => { crate::families::transactions::try_head_kick_member(deps, info, member) } - ExecuteMsg::CreateFamilyOnBehalf { - owner_address, - label, - } => crate::families::transactions::try_create_family_on_behalf( - deps, - info, - owner_address, - label, - ), - ExecuteMsg::JoinFamilyOnBehalf { - member_address, - join_permit, - family_head, - } => crate::families::transactions::try_join_family_on_behalf( - deps, - info, - member_address, - join_permit, - family_head, - ), - ExecuteMsg::LeaveFamilyOnBehalf { - member_address, - family_head, - } => crate::families::transactions::try_leave_family_on_behalf( - deps, - info, - member_address, - family_head, - ), - ExecuteMsg::KickFamilyMemberOnBehalf { - head_address, - member, - } => crate::families::transactions::try_head_kick_member_on_behalf( - deps, - info, - head_address, - member, - ), // state/sys-params-related ExecuteMsg::UpdateRewardingValidatorAddress { address } => { crate::mixnet_contract_settings::transactions::try_update_rewarding_validator_address( @@ -232,62 +194,23 @@ pub fn execute( cost_params, owner_signature, ), - ExecuteMsg::BondMixnodeOnBehalf { - mix_node, - cost_params, - owner, - owner_signature, - } => crate::mixnodes::transactions::try_add_mixnode_on_behalf( - deps, - env, - info, - mix_node, - cost_params, - owner, - owner_signature, - ), ExecuteMsg::PledgeMore {} => { crate::mixnodes::transactions::try_increase_pledge(deps, env, info) } - ExecuteMsg::PledgeMoreOnBehalf { owner } => { - crate::mixnodes::transactions::try_increase_pledge_on_behalf(deps, env, info, owner) - } ExecuteMsg::DecreasePledge { decrease_by } => { crate::mixnodes::transactions::try_decrease_pledge(deps, env, info, decrease_by) } - ExecuteMsg::DecreasePledgeOnBehalf { owner, decrease_by } => { - crate::mixnodes::transactions::try_decrease_pledge_on_behalf( - deps, - env, - info, - decrease_by, - owner, - ) - } ExecuteMsg::UnbondMixnode {} => { crate::mixnodes::transactions::try_remove_mixnode(deps, env, info) } - ExecuteMsg::UnbondMixnodeOnBehalf { owner } => { - crate::mixnodes::transactions::try_remove_mixnode_on_behalf(deps, env, info, owner) - } ExecuteMsg::UpdateMixnodeCostParams { new_costs } => { crate::mixnodes::transactions::try_update_mixnode_cost_params( deps, env, info, new_costs, ) } - ExecuteMsg::UpdateMixnodeCostParamsOnBehalf { new_costs, owner } => { - crate::mixnodes::transactions::try_update_mixnode_cost_params_on_behalf( - deps, env, info, new_costs, owner, - ) - } ExecuteMsg::UpdateMixnodeConfig { new_config } => { crate::mixnodes::transactions::try_update_mixnode_config(deps, info, new_config) } - ExecuteMsg::UpdateMixnodeConfigOnBehalf { new_config, owner } => { - crate::mixnodes::transactions::try_update_mixnode_config_on_behalf( - deps, info, new_config, owner, - ) - } // gateway-related: ExecuteMsg::BondGateway { @@ -300,52 +223,22 @@ pub fn execute( gateway, owner_signature, ), - ExecuteMsg::BondGatewayOnBehalf { - gateway, - owner, - owner_signature, - } => crate::gateways::transactions::try_add_gateway_on_behalf( - deps, - env, - info, - gateway, - owner, - owner_signature, - ), ExecuteMsg::UnbondGateway {} => { crate::gateways::transactions::try_remove_gateway(deps, info) } - ExecuteMsg::UnbondGatewayOnBehalf { owner } => { - crate::gateways::transactions::try_remove_gateway_on_behalf(deps, info, owner) - } ExecuteMsg::UpdateGatewayConfig { new_config } => { crate::gateways::transactions::try_update_gateway_config(deps, info, new_config) } - ExecuteMsg::UpdateGatewayConfigOnBehalf { new_config, owner } => { - crate::gateways::transactions::try_update_gateway_config_on_behalf( - deps, info, new_config, owner, - ) - } // delegation-related: ExecuteMsg::DelegateToMixnode { mix_id } => { crate::delegations::transactions::try_delegate_to_mixnode(deps, env, info, mix_id) } - ExecuteMsg::DelegateToMixnodeOnBehalf { mix_id, delegate } => { - crate::delegations::transactions::try_delegate_to_mixnode_on_behalf( - deps, env, info, mix_id, delegate, - ) - } ExecuteMsg::UndelegateFromMixnode { mix_id } => { crate::delegations::transactions::try_remove_delegation_from_mixnode( deps, env, info, mix_id, ) } - ExecuteMsg::UndelegateFromMixnodeOnBehalf { mix_id, delegate } => { - crate::delegations::transactions::try_remove_delegation_from_mixnode_on_behalf( - deps, env, info, mix_id, delegate, - ) - } // reward-related ExecuteMsg::RewardMixnode { @@ -356,16 +249,37 @@ pub fn execute( ExecuteMsg::WithdrawOperatorReward {} => { crate::rewards::transactions::try_withdraw_operator_reward(deps, info) } - ExecuteMsg::WithdrawOperatorRewardOnBehalf { owner } => { - crate::rewards::transactions::try_withdraw_operator_reward_on_behalf(deps, info, owner) - } ExecuteMsg::WithdrawDelegatorReward { mix_id } => { crate::rewards::transactions::try_withdraw_delegator_reward(deps, info, mix_id) } - ExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, owner } => { - crate::rewards::transactions::try_withdraw_delegator_reward_on_behalf( - deps, info, mix_id, owner, - ) + + // vesting migration: + ExecuteMsg::MigrateVestedMixNode { .. } => { + crate::vesting_migration::try_migrate_vested_mixnode(deps, info) + } + ExecuteMsg::MigrateVestedDelegation { mix_id } => { + crate::vesting_migration::try_migrate_vested_delegation(deps, info, mix_id) + } + + // legacy vesting + ExecuteMsg::CreateFamilyOnBehalf { .. } + | ExecuteMsg::JoinFamilyOnBehalf { .. } + | ExecuteMsg::LeaveFamilyOnBehalf { .. } + | ExecuteMsg::KickFamilyMemberOnBehalf { .. } + | ExecuteMsg::BondMixnodeOnBehalf { .. } + | ExecuteMsg::PledgeMoreOnBehalf { .. } + | ExecuteMsg::DecreasePledgeOnBehalf { .. } + | ExecuteMsg::UnbondMixnodeOnBehalf { .. } + | ExecuteMsg::UpdateMixnodeCostParamsOnBehalf { .. } + | ExecuteMsg::UpdateMixnodeConfigOnBehalf { .. } + | ExecuteMsg::BondGatewayOnBehalf { .. } + | ExecuteMsg::UnbondGatewayOnBehalf { .. } + | ExecuteMsg::UpdateGatewayConfigOnBehalf { .. } + | ExecuteMsg::DelegateToMixnodeOnBehalf { .. } + | ExecuteMsg::UndelegateFromMixnodeOnBehalf { .. } + | ExecuteMsg::WithdrawOperatorRewardOnBehalf { .. } + | ExecuteMsg::WithdrawDelegatorRewardOnBehalf { .. } => { + Err(MixnetContractError::DisabledVestingOperation) } // testing-only @@ -607,13 +521,15 @@ pub fn query( #[entry_point] pub fn migrate( - deps: DepsMut<'_>, + mut deps: DepsMut<'_>, _env: Env, msg: MigrateMsg, ) -> Result { set_build_information!(deps.storage)?; cw2::ensure_from_older_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + crate::queued_migrations::vesting_purge(deps.branch())?; + // due to circular dependency on contract addresses (i.e. mixnet contract requiring vesting contract address // and vesting contract requiring the mixnet contract address), if we ever want to deploy any new fresh // environment, one of the contracts will HAVE TO go through a migration diff --git a/contracts/mixnet/src/delegations/queries.rs b/contracts/mixnet/src/delegations/queries.rs index 5a5d07c32ff..01e7b656486 100644 --- a/contracts/mixnet/src/delegations/queries.rs +++ b/contracts/mixnet/src/delegations/queries.rs @@ -302,10 +302,7 @@ mod tests { mod delegator_delegations { use super::*; - use crate::delegations::transactions::try_delegate_to_mixnode_on_behalf; - use crate::support::tests::fixtures::TEST_COIN_DENOM; - use cosmwasm_std::testing::mock_info; - use cosmwasm_std::{coin, Addr}; + use cosmwasm_std::Addr; #[test] fn obeys_limits() { @@ -453,25 +450,10 @@ mod tests { #[test] fn all_retrieved_delegations_are_from_the_specified_delegator() { let mut test = TestSetup::new(); - let env = test.env(); + // it means we have, for example, delegation from "delegator1" towards mix1, mix2, ...., from "delegator2" towards mix1, mix2, ...., etc add_dummy_mixes_with_delegations(&mut test, 50, 100); - // add some proxies while we're at it to make sure they're queried for separately - let with_proxy = "delegator42"; - let vesting_contract = test.vesting_contract(); - for mix_id in 1..=25 { - try_delegate_to_mixnode_on_behalf( - test.deps_mut(), - env.clone(), - mock_info(vesting_contract.as_ref(), &[coin(100_000, TEST_COIN_DENOM)]), - mix_id, - with_proxy.into(), - ) - .unwrap(); - } - test.execute_all_pending_events(); - // make few queries let res1 = query_delegator_delegations_paged(test.deps(), "delegator2".into(), None, None) @@ -490,59 +472,6 @@ mod tests { .delegations .into_iter() .all(|d| d.owner == Addr::unchecked("delegator35"))); - - let with_proxy_full = - query_delegator_delegations_paged(test.deps(), with_proxy.into(), None, None) - .unwrap(); - assert_eq!(with_proxy_full.delegations.len(), 125); - - // all delegations have correct owner - assert!(with_proxy_full - .delegations - .iter() - .all(|d| d.owner == Addr::unchecked(with_proxy))); - - // and we have 100 delegations without proxy and 25 with - let no_proxy = with_proxy_full - .delegations - .iter() - .filter(|d| d.proxy.is_none()) - .count(); - assert_eq!(no_proxy, 100); - let proxy = with_proxy_full - .delegations - .iter() - .filter(|d| d.proxy.is_some()) - .count(); - assert_eq!(proxy, 25); - - assert!(with_proxy_full - .delegations - .iter() - .filter(|d| d.proxy.is_some()) - .all(|d| d.proxy.as_ref().unwrap() == vesting_contract)); - - // now make sure that if we do it in paged manner, we'll get exactly the same result - let per_page = Some(15); - let mut delegations = Vec::new(); - let mut start_after = None; - loop { - let mut paged_response = query_delegator_delegations_paged( - test.deps(), - with_proxy.into(), - start_after, - per_page, - ) - .unwrap(); - delegations.append(&mut paged_response.delegations); - - if let Some(start_after_res) = paged_response.start_next_after { - start_after = Some(start_after_res) - } else { - break; - } - } - assert_eq!(with_proxy_full.delegations, delegations) } } diff --git a/contracts/mixnet/src/delegations/transactions.rs b/contracts/mixnet/src/delegations/transactions.rs index adc70c54cea..b91c6c85823 100644 --- a/contracts/mixnet/src/delegations/transactions.rs +++ b/contracts/mixnet/src/delegations/transactions.rs @@ -1,14 +1,12 @@ -// Copyright 2021-2023 - Nym Technologies SA +// Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use super::storage; use crate::interval::storage as interval_storage; use crate::mixnet_contract_settings::storage as mixnet_params_storage; use crate::mixnodes::storage as mixnodes_storage; -use crate::support::helpers::{ - ensure_epoch_in_progress_state, ensure_sent_by_vesting_contract, validate_delegation_stake, -}; -use cosmwasm_std::{Addr, Coin, DepsMut, Env, MessageInfo, Response}; +use crate::support::helpers::{ensure_epoch_in_progress_state, validate_delegation_stake}; +use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::events::{ new_pending_delegation_event, new_pending_undelegation_event, @@ -21,30 +19,6 @@ pub(crate) fn try_delegate_to_mixnode( env: Env, info: MessageInfo, mix_id: MixId, -) -> Result { - _try_delegate_to_mixnode(deps, env, mix_id, info.sender, info.funds, None) -} - -pub(crate) fn try_delegate_to_mixnode_on_behalf( - deps: DepsMut<'_>, - env: Env, - info: MessageInfo, - mix_id: MixId, - delegate: String, -) -> Result { - ensure_sent_by_vesting_contract(&info, deps.storage)?; - - let delegate = deps.api.addr_validate(&delegate)?; - _try_delegate_to_mixnode(deps, env, mix_id, delegate, info.funds, Some(info.sender)) -} - -pub(crate) fn _try_delegate_to_mixnode( - deps: DepsMut<'_>, - env: Env, - mix_id: MixId, - delegate: Addr, - amount: Vec, - proxy: Option, ) -> Result { // delegation is only allowed if the epoch is currently not in the process of being advanced ensure_epoch_in_progress_state(deps.storage)?; @@ -52,7 +26,7 @@ pub(crate) fn _try_delegate_to_mixnode( // check if the delegation contains any funds of the appropriate denomination let contract_state = mixnet_params_storage::CONTRACT_STATE.load(deps.storage)?; let delegation = validate_delegation_stake( - amount, + info.funds, contract_state.params.minimum_mixnode_delegation, contract_state.rewarding_denom, )?; @@ -67,14 +41,9 @@ pub(crate) fn _try_delegate_to_mixnode( } // push the event onto the queue and wait for it to be picked up at the end of the epoch - let cosmos_event = new_pending_delegation_event(&delegate, &proxy, &delegation, mix_id); - - let epoch_event = PendingEpochEventKind::Delegate { - owner: delegate, - mix_id, - amount: delegation, - proxy, - }; + let cosmos_event = new_pending_delegation_event(&info.sender, &delegation, mix_id); + + let epoch_event = PendingEpochEventKind::new_delegate(info.sender, mix_id, delegation); interval_storage::push_new_epoch_event(deps.storage, &env, epoch_event)?; Ok(Response::new().add_event(cosmos_event)) @@ -85,35 +54,12 @@ pub(crate) fn try_remove_delegation_from_mixnode( env: Env, info: MessageInfo, mix_id: MixId, -) -> Result { - _try_remove_delegation_from_mixnode(deps, env, mix_id, info.sender, None) -} - -pub(crate) fn try_remove_delegation_from_mixnode_on_behalf( - deps: DepsMut<'_>, - env: Env, - info: MessageInfo, - mix_id: MixId, - delegate: String, -) -> Result { - ensure_sent_by_vesting_contract(&info, deps.storage)?; - - let delegate = deps.api.addr_validate(&delegate)?; - _try_remove_delegation_from_mixnode(deps, env, mix_id, delegate, Some(info.sender)) -} - -pub(crate) fn _try_remove_delegation_from_mixnode( - deps: DepsMut<'_>, - env: Env, - mix_id: MixId, - delegate: Addr, - proxy: Option, ) -> Result { // undelegation is only allowed if the epoch is currently not in the process of being advanced ensure_epoch_in_progress_state(deps.storage)?; // see if the delegation even exists - let storage_key = Delegation::generate_storage_key(mix_id, &delegate, proxy.as_ref()); + let storage_key = Delegation::generate_storage_key(mix_id, &info.sender, None); if storage::delegations() .may_load(deps.storage, storage_key)? @@ -121,19 +67,15 @@ pub(crate) fn _try_remove_delegation_from_mixnode( { return Err(MixnetContractError::NoMixnodeDelegationFound { mix_id, - address: delegate.into_string(), - proxy: proxy.map(Addr::into_string), + address: info.sender.into_string(), + proxy: None, }); } // push the event onto the queue and wait for it to be picked up at the end of the epoch - let cosmos_event = new_pending_undelegation_event(&delegate, &proxy, mix_id); + let cosmos_event = new_pending_undelegation_event(&info.sender, mix_id); - let epoch_event = PendingEpochEventKind::Undelegate { - owner: delegate, - mix_id, - proxy, - }; + let epoch_event = PendingEpochEventKind::new_undelegate(info.sender, mix_id); interval_storage::push_new_epoch_event(deps.storage, &env, epoch_event)?; Ok(Response::new().add_event(cosmos_event)) @@ -151,7 +93,7 @@ mod tests { use crate::support::tests::fixtures::TEST_COIN_DENOM; use crate::support::tests::test_helpers::TestSetup; use cosmwasm_std::testing::mock_info; - use cosmwasm_std::{coin, Decimal}; + use cosmwasm_std::{coin, Addr, Decimal}; use mixnet_contract_common::{EpochState, EpochStatus}; #[test] @@ -368,66 +310,18 @@ mod tests { let mix_id = test.add_dummy_mixnode("mix-owner", None); let amount1 = coin(100_000_000, TEST_COIN_DENOM); - let amount2 = coin(50_000_000, TEST_COIN_DENOM); let sender1 = mock_info(owner, &[amount1.clone()]); - let sender2 = mock_info(test.vesting_contract().as_str(), &[amount2.clone()]); try_delegate_to_mixnode(test.deps_mut(), env.clone(), sender1, mix_id).unwrap(); - try_delegate_to_mixnode_on_behalf(test.deps_mut(), env, sender2, mix_id, owner.into()) - .unwrap(); let events = test.pending_epoch_events(); assert_eq!( events[0].kind, - PendingEpochEventKind::Delegate { - owner: Addr::unchecked(owner), - mix_id, - amount: amount1, - proxy: None - } - ); - - assert_eq!( - events[1].kind, - PendingEpochEventKind::Delegate { - owner: Addr::unchecked(owner), - mix_id, - amount: amount2, - proxy: Some(test.vesting_contract()) - } + PendingEpochEventKind::new_delegate(Addr::unchecked(owner), mix_id, amount1,) ); } - - #[test] - fn fails_for_illegal_proxy() { - let mut test = TestSetup::new(); - let env = test.env(); - - let illegal_proxy = Addr::unchecked("not-vesting-contract"); - let vesting_contract = test.vesting_contract(); - - let owner = "delegator"; - let mix_id = test.add_dummy_mixnode("mix-owner", None); - - let res = try_delegate_to_mixnode_on_behalf( - test.deps_mut(), - env, - mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]), - mix_id, - owner.into(), - ) - .unwrap_err(); - - assert_eq!( - res, - MixnetContractError::SenderIsNotVestingContract { - received: illegal_proxy, - vesting_contract - } - ) - } } #[cfg(test)] @@ -573,40 +467,5 @@ mod tests { ); assert!(res.is_ok()); } - - #[test] - fn fails_for_illegal_proxy() { - let mut test = TestSetup::new(); - let env = test.env(); - - let illegal_proxy = Addr::unchecked("not-vesting-contract"); - let vesting_contract = test.vesting_contract(); - - let owner = "delegator"; - let mix_id = test.add_dummy_mixnode("mix-owner", None); - test.add_immediate_delegation_with_illegal_proxy( - owner, - 10000u32, - mix_id, - illegal_proxy.clone(), - ); - - let res = try_remove_delegation_from_mixnode_on_behalf( - test.deps_mut(), - env, - mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]), - mix_id, - owner.into(), - ) - .unwrap_err(); - - assert_eq!( - res, - MixnetContractError::SenderIsNotVestingContract { - received: illegal_proxy, - vesting_contract - } - ) - } } } diff --git a/contracts/mixnet/src/families/signature_helpers.rs b/contracts/mixnet/src/families/signature_helpers.rs index 222c7548cd2..3c6bb796267 100644 --- a/contracts/mixnet/src/families/signature_helpers.rs +++ b/contracts/mixnet/src/families/signature_helpers.rs @@ -4,7 +4,7 @@ use crate::mixnodes::storage as mixnodes_storage; use crate::signing::storage as signing_storage; use crate::support::helpers::decode_ed25519_identity_key; -use cosmwasm_std::{Addr, Deps}; +use cosmwasm_std::Deps; use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::families::FamilyHead; use mixnet_contract_common::{construct_family_join_permit, IdentityKeyRef}; @@ -13,7 +13,6 @@ use nym_contracts_common::signing::{MessageSignature, Verifier}; pub(crate) fn verify_family_join_permit( deps: Deps<'_>, granter: FamilyHead, - proxy: Option, member: IdentityKeyRef, signature: MessageSignature, ) -> Result<(), MixnetContractError> { @@ -32,7 +31,7 @@ pub(crate) fn verify_family_join_permit( }); }; let nonce = signing_storage::get_signing_nonce(deps.storage, head_mixnode.owner)?; - let msg = construct_family_join_permit(nonce, granter, proxy, member.to_owned()); + let msg = construct_family_join_permit(nonce, granter, member.to_owned()); if deps.api.verify_message(msg, signature, &public_key)? { Ok(()) diff --git a/contracts/mixnet/src/families/transactions.rs b/contracts/mixnet/src/families/transactions.rs index 732acddb43b..ee88f98e635 100644 --- a/contracts/mixnet/src/families/transactions.rs +++ b/contracts/mixnet/src/families/transactions.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 - Nym Technologies SA +// Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use super::storage::{ @@ -7,41 +7,20 @@ use super::storage::{ }; use crate::families::queries::get_family_by_label; use crate::families::signature_helpers::verify_family_join_permit; -use crate::support::helpers::{ensure_bonded, ensure_sent_by_vesting_contract}; -use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response}; +use crate::support::helpers::ensure_bonded; +use cosmwasm_std::{DepsMut, MessageInfo, Response}; use mixnet_contract_common::families::{Family, FamilyHead}; use mixnet_contract_common::{error::MixnetContractError, IdentityKey}; use nym_contracts_common::signing::MessageSignature; /// Creates a new MixNode family with senders node as head -pub fn try_create_family( +pub(crate) fn try_create_family( deps: DepsMut, info: MessageInfo, label: String, -) -> Result { - _try_create_family(deps, &info.sender, label, None) -} - -pub fn try_create_family_on_behalf( - deps: DepsMut, - info: MessageInfo, - owner_address: String, - label: String, -) -> Result { - ensure_sent_by_vesting_contract(&info, deps.storage)?; - - let owner_address = deps.api.addr_validate(&owner_address)?; - _try_create_family(deps, &owner_address, label, Some(info.sender)) -} - -fn _try_create_family( - deps: DepsMut, - owner: &Addr, - label: String, - proxy: Option, ) -> Result { let existing_bond = - crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?; + crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?; ensure_bonded(&existing_bond)?; @@ -60,43 +39,19 @@ fn _try_create_family( return Err(MixnetContractError::FamilyWithLabelExists(label)); } - let family = Family::new(family_head, proxy, label); + let family = Family::new(family_head, label); save_family(&family, deps.storage)?; Ok(Response::default()) } -pub fn try_join_family( - deps: DepsMut, - info: MessageInfo, - join_permit: MessageSignature, - family_head: FamilyHead, -) -> Result { - _try_join_family(deps, &info.sender, join_permit, family_head, None) -} - -pub fn try_join_family_on_behalf( +pub(crate) fn try_join_family( deps: DepsMut, info: MessageInfo, - member_address: String, - join_permit: MessageSignature, - family_head: FamilyHead, -) -> Result { - ensure_sent_by_vesting_contract(&info, deps.storage)?; - - let member_address = deps.api.addr_validate(&member_address)?; - let proxy = Some(info.sender); - _try_join_family(deps, &member_address, join_permit, family_head, proxy) -} - -fn _try_join_family( - deps: DepsMut, - owner: &Addr, join_permit: MessageSignature, family_head: FamilyHead, - proxy: Option, ) -> Result { let existing_bond = - crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?; + crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?; ensure_bonded(&existing_bond)?; @@ -116,7 +71,6 @@ fn _try_join_family( verify_family_join_permit( deps.as_ref(), family_head.clone(), - proxy, existing_bond.identity(), join_permit, )?; @@ -128,33 +82,13 @@ fn _try_join_family( Ok(Response::default()) } -pub fn try_leave_family( - deps: DepsMut, - info: MessageInfo, - family_head: FamilyHead, -) -> Result { - _try_leave_family(deps, &info.sender, family_head) -} - -pub fn try_leave_family_on_behalf( +pub(crate) fn try_leave_family( deps: DepsMut, info: MessageInfo, - member_address: String, - family_head: FamilyHead, -) -> Result { - ensure_sent_by_vesting_contract(&info, deps.storage)?; - - let member_address = deps.api.addr_validate(&member_address)?; - _try_leave_family(deps, &member_address, family_head) -} - -fn _try_leave_family( - deps: DepsMut, - owner: &Addr, family_head: FamilyHead, ) -> Result { let existing_bond = - crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?; + crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?; ensure_bonded(&existing_bond)?; @@ -178,32 +112,13 @@ fn _try_leave_family( Ok(Response::default()) } -pub fn try_head_kick_member( +pub(crate) fn try_head_kick_member( deps: DepsMut, info: MessageInfo, member: IdentityKey, ) -> Result { - _try_head_kick_member(deps, &info.sender, member) -} - -pub fn try_head_kick_member_on_behalf( - deps: DepsMut, - info: MessageInfo, - head_address: String, - member: IdentityKey, -) -> Result { - ensure_sent_by_vesting_contract(&info, deps.storage)?; - - let head_address = deps.api.addr_validate(&head_address)?; - _try_head_kick_member(deps, &head_address, member) -} - -fn _try_head_kick_member( - deps: DepsMut, - owner: &Addr, - member: IdentityKey, -) -> Result { - let head_bond = crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?; + let head_bond = + crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?; // make sure we're still in the mixnet ensure_bonded(&head_bond)?; @@ -321,7 +236,7 @@ mod test { assert_eq!(family.head_identity(), family_head.identity()); let join_permit = - test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key, false); + test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key); try_join_family( test.deps_mut(), @@ -345,7 +260,7 @@ mod test { ); let new_join_permit = - test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key, false); + test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key); try_join_family( test.deps_mut(), @@ -373,189 +288,4 @@ mod test { !is_family_member(test.deps().storage, &family, &member_mixnode.identity_key).unwrap() ); } - - #[cfg(test)] - mod creating_family { - use super::*; - - #[test] - fn fails_for_illegal_proxy() { - let mut test = TestSetup::new(); - - let illegal_proxy = Addr::unchecked("not-vesting-contract"); - let vesting_contract = test.vesting_contract(); - - let head = "alice"; - - test.add_dummy_mixnode(head, None); - - let res = try_create_family_on_behalf( - test.deps_mut(), - mock_info(illegal_proxy.as_ref(), &[]), - head.to_string(), - "label".to_string(), - ) - .unwrap_err(); - - assert_eq!( - res, - MixnetContractError::SenderIsNotVestingContract { - received: illegal_proxy, - vesting_contract - } - ) - } - } - - #[cfg(test)] - mod joining_family { - use super::*; - - #[test] - fn fails_for_illegal_proxy() { - let mut test = TestSetup::new(); - - let illegal_proxy = Addr::unchecked("not-vesting-contract"); - let vesting_contract = test.vesting_contract(); - - let head = "alice"; - let label = "family"; - let new_member = "vin-diesel"; - - let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label); - let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None); - - let join_permit = test.generate_family_join_permit( - &head_keys, - &member_keys.public_key().to_base58_string(), - false, - ); - - let head_identity = head_keys.public_key().to_base58_string(); - let family_head = FamilyHead::new(head_identity); - let res = try_join_family_on_behalf( - test.deps_mut(), - mock_info(illegal_proxy.as_ref(), &[]), - new_member.to_string(), - join_permit, - family_head, - ) - .unwrap_err(); - - assert_eq!( - res, - MixnetContractError::SenderIsNotVestingContract { - received: illegal_proxy, - vesting_contract - } - ) - } - } - - #[cfg(test)] - mod leaving_family { - use super::*; - - #[test] - fn fails_for_illegal_proxy() { - let mut test = TestSetup::new(); - - let illegal_proxy = Addr::unchecked("not-vesting-contract"); - let vesting_contract = test.vesting_contract(); - - let head = "alice"; - let label = "family"; - let new_member = "vin-diesel"; - - let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label); - let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None); - - let join_permit = test.generate_family_join_permit( - &head_keys, - &member_keys.public_key().to_base58_string(), - true, - ); - - let head_identity = head_keys.public_key().to_base58_string(); - let family_head = FamilyHead::new(head_identity); - try_join_family_on_behalf( - test.deps_mut(), - mock_info(vesting_contract.as_ref(), &[]), - new_member.to_string(), - join_permit, - family_head.clone(), - ) - .unwrap(); - - let res = try_leave_family_on_behalf( - test.deps_mut(), - mock_info(illegal_proxy.as_ref(), &[]), - new_member.to_string(), - family_head, - ) - .unwrap_err(); - - assert_eq!( - res, - MixnetContractError::SenderIsNotVestingContract { - received: illegal_proxy, - vesting_contract - } - ) - } - } - - #[cfg(test)] - mod kicking_family_member { - use super::*; - - #[test] - fn fails_for_illegal_proxy() { - let mut test = TestSetup::new(); - - let illegal_proxy = Addr::unchecked("not-vesting-contract"); - let vesting_contract = test.vesting_contract(); - - let head = "alice"; - let label = "family"; - let new_member = "vin-diesel"; - - let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label); - let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None); - - let join_permit = test.generate_family_join_permit( - &head_keys, - &member_keys.public_key().to_base58_string(), - true, - ); - - let head_identity = head_keys.public_key().to_base58_string(); - let family_head = FamilyHead::new(head_identity); - - try_join_family_on_behalf( - test.deps_mut(), - mock_info(vesting_contract.as_ref(), &[]), - new_member.to_string(), - join_permit, - family_head, - ) - .unwrap(); - - let res = try_head_kick_member_on_behalf( - test.deps_mut(), - mock_info(illegal_proxy.as_ref(), &[]), - head.to_string(), - member_keys.public_key().to_base58_string(), - ) - .unwrap_err(); - - assert_eq!( - res, - MixnetContractError::SenderIsNotVestingContract { - received: illegal_proxy, - vesting_contract - } - ) - } - } } diff --git a/contracts/mixnet/src/gateways/signature_helpers.rs b/contracts/mixnet/src/gateways/signature_helpers.rs index e6ce7f5ad50..46d37c60731 100644 --- a/contracts/mixnet/src/gateways/signature_helpers.rs +++ b/contracts/mixnet/src/gateways/signature_helpers.rs @@ -12,7 +12,6 @@ use nym_contracts_common::signing::Verifier; pub(crate) fn verify_gateway_bonding_signature( deps: Deps<'_>, sender: Addr, - proxy: Option, pledge: Coin, gateway: Gateway, signature: MessageSignature, @@ -22,7 +21,7 @@ pub(crate) fn verify_gateway_bonding_signature( // reconstruct the payload let nonce = signing_storage::get_signing_nonce(deps.storage, sender.clone())?; - let msg = construct_gateway_bonding_sign_payload(nonce, sender, proxy, pledge, gateway); + let msg = construct_gateway_bonding_sign_payload(nonce, sender, pledge, gateway); if deps.api.verify_message(msg, signature, &public_key)? { Ok(()) diff --git a/contracts/mixnet/src/gateways/transactions.rs b/contracts/mixnet/src/gateways/transactions.rs index f747c4c7ba2..6075c258941 100644 --- a/contracts/mixnet/src/gateways/transactions.rs +++ b/contracts/mixnet/src/gateways/transactions.rs @@ -1,4 +1,4 @@ -// Copyright 2021-2023 - Nym Technologies SA +// Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use super::helpers::must_get_gateway_bond_by_owner; @@ -6,10 +6,8 @@ use super::storage; use crate::gateways::signature_helpers::verify_gateway_bonding_signature; use crate::mixnet_contract_settings::storage as mixnet_params_storage; use crate::signing::storage as signing_storage; -use crate::support::helpers::{ - ensure_no_existing_bond, ensure_proxy_match, ensure_sent_by_vesting_contract, validate_pledge, -}; -use cosmwasm_std::{wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response}; +use crate::support::helpers::{ensure_no_existing_bond, validate_pledge}; +use cosmwasm_std::{BankMsg, DepsMut, Env, MessageInfo, Response}; use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::events::{ new_gateway_bonding_event, new_gateway_config_update_event, new_gateway_unbonding_event, @@ -17,72 +15,28 @@ use mixnet_contract_common::events::{ use mixnet_contract_common::gateway::GatewayConfigUpdate; use mixnet_contract_common::{Gateway, GatewayBond}; use nym_contracts_common::signing::MessageSignature; -use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg; - -pub fn try_add_gateway( - deps: DepsMut<'_>, - env: Env, - info: MessageInfo, - gateway: Gateway, - owner_signature: MessageSignature, -) -> Result { - _try_add_gateway( - deps, - env, - gateway, - info.funds, - info.sender, - owner_signature, - None, - ) -} - -pub fn try_add_gateway_on_behalf( - deps: DepsMut<'_>, - env: Env, - info: MessageInfo, - gateway: Gateway, - owner: String, - owner_signature: MessageSignature, -) -> Result { - ensure_sent_by_vesting_contract(&info, deps.storage)?; - - let proxy = info.sender; - let owner = deps.api.addr_validate(&owner)?; - _try_add_gateway( - deps, - env, - gateway, - info.funds, - owner, - owner_signature, - Some(proxy), - ) -} // TODO: perhaps also require the user to explicitly provide what it thinks is the current nonce // so that we could return a better error message if it doesn't match? -pub(crate) fn _try_add_gateway( +pub(crate) fn try_add_gateway( deps: DepsMut<'_>, env: Env, + info: MessageInfo, gateway: Gateway, - pledge: Vec, - owner: Addr, owner_signature: MessageSignature, - proxy: Option, ) -> Result { // check if the pledge contains any funds of the appropriate denomination let minimum_pledge = mixnet_params_storage::minimum_gateway_pledge(deps.storage)?; - let pledge = validate_pledge(pledge, minimum_pledge)?; + let pledge = validate_pledge(info.funds, minimum_pledge)?; // if the client has an active bonded mixnode or gateway, don't allow bonding - ensure_no_existing_bond(&owner, deps.storage)?; + ensure_no_existing_bond(&info.sender, deps.storage)?; // check if somebody else has already bonded a gateway with this identity if let Some(existing_bond) = storage::gateways().may_load(deps.storage, &gateway.identity_key)? { - if existing_bond.owner != owner { + if existing_bond.owner != info.sender { return Err(MixnetContractError::DuplicateGateway { owner: existing_bond.owner, }); @@ -92,105 +46,62 @@ pub(crate) fn _try_add_gateway( // check if this sender actually owns the gateway by checking the signature verify_gateway_bonding_signature( deps.as_ref(), - owner.clone(), - proxy.clone(), + info.sender.clone(), pledge.clone(), gateway.clone(), owner_signature, )?; // update the signing nonce associated with this sender so that the future signature would be made on the new value - signing_storage::increment_signing_nonce(deps.storage, owner.clone())?; + signing_storage::increment_signing_nonce(deps.storage, info.sender.clone())?; let gateway_identity = gateway.identity_key.clone(); let bond = GatewayBond::new( pledge.clone(), - owner.clone(), + info.sender.clone(), env.block.height, gateway, - proxy.clone(), ); storage::gateways().save(deps.storage, bond.identity(), &bond)?; Ok(Response::new().add_event(new_gateway_bonding_event( - &owner, - &proxy, + &info.sender, &pledge, &gateway_identity, ))) } -pub fn try_remove_gateway_on_behalf( - deps: DepsMut<'_>, - info: MessageInfo, - owner: String, -) -> Result { - ensure_sent_by_vesting_contract(&info, deps.storage)?; - - let proxy = info.sender; - let owner = deps.api.addr_validate(&owner)?; - _try_remove_gateway(deps, owner, Some(proxy)) -} - -pub fn try_remove_gateway( +pub(crate) fn try_remove_gateway( deps: DepsMut<'_>, info: MessageInfo, -) -> Result { - _try_remove_gateway(deps, info.sender, None) -} - -pub(crate) fn _try_remove_gateway( - deps: DepsMut<'_>, - owner: Addr, - proxy: Option, ) -> Result { // try to find the node of the sender let gateway_bond = match storage::gateways() .idx .owner - .item(deps.storage, owner.clone())? + .item(deps.storage, info.sender.clone())? { Some(record) => record.1, - None => return Err(MixnetContractError::NoAssociatedGatewayBond { owner }), + None => return Err(MixnetContractError::NoAssociatedGatewayBond { owner: info.sender }), }; - if proxy != gateway_bond.proxy { - return Err(MixnetContractError::ProxyMismatch { - existing: gateway_bond - .proxy - .map_or_else(|| "None".to_string(), |a| a.as_str().to_string()), - incoming: proxy.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()), - }); - } - // send bonded funds back to the bond owner let return_tokens = BankMsg::Send { - to_address: proxy.as_ref().unwrap_or(&owner).to_string(), + to_address: info.sender.to_string(), amount: vec![gateway_bond.pledge_amount()], }; // remove the bond storage::gateways().remove(deps.storage, gateway_bond.identity())?; - let mut response = Response::new().add_message(return_tokens); - - if let Some(proxy) = &proxy { - let msg = VestingContractExecuteMsg::TrackUnbondGateway { - owner: owner.as_str().to_string(), - amount: gateway_bond.pledge_amount(), - }; - - let track_unbond_message = wasm_execute(proxy, &msg, vec![])?; - response = response.add_message(track_unbond_message); - } - - Ok(response.add_event(new_gateway_unbonding_event( - &owner, - &proxy, - &gateway_bond.pledge_amount, - gateway_bond.identity(), - ))) + Ok(Response::new() + .add_message(return_tokens) + .add_event(new_gateway_unbonding_event( + &info.sender, + &gateway_bond.pledge_amount, + gateway_bond.identity(), + ))) } pub(crate) fn try_update_gateway_config( @@ -198,36 +109,9 @@ pub(crate) fn try_update_gateway_config( info: MessageInfo, new_config: GatewayConfigUpdate, ) -> Result { - let owner = info.sender; - _try_update_gateway_config(deps, new_config, owner, None) -} - -pub(crate) fn try_update_gateway_config_on_behalf( - deps: DepsMut, - info: MessageInfo, - new_config: GatewayConfigUpdate, - owner: String, -) -> Result { - ensure_sent_by_vesting_contract(&info, deps.storage)?; - - let owner = deps.api.addr_validate(&owner)?; - let proxy = info.sender; - _try_update_gateway_config(deps, new_config, owner, Some(proxy)) -} - -pub(crate) fn _try_update_gateway_config( - deps: DepsMut, - new_config: GatewayConfigUpdate, - owner: Addr, - proxy: Option, -) -> Result { - let existing_bond = must_get_gateway_bond_by_owner(deps.storage, &owner)?; - ensure_proxy_match(&proxy, &existing_bond.proxy)?; - - let cfg_update_event = new_gateway_config_update_event(&owner, &proxy, &new_config); + let existing_bond = must_get_gateway_bond_by_owner(deps.storage, &info.sender)?; + let cfg_update_event = new_gateway_config_update_event(&info.sender, &new_config); - // clippy beta 1.70.0-beta.1 false positive - #[allow(clippy::redundant_clone)] let mut updated_bond = existing_bond.clone(); updated_bond.gateway.host = new_config.host; updated_bond.gateway.mix_port = new_config.mix_port; @@ -254,10 +138,10 @@ pub mod tests { use crate::mixnet_contract_settings::storage::minimum_gateway_pledge; use crate::support::tests; use crate::support::tests::fixtures; - use crate::support::tests::fixtures::{good_gateway_pledge, good_mixnode_pledge}; + use crate::support::tests::fixtures::good_mixnode_pledge; use crate::support::tests::test_helpers::TestSetup; use cosmwasm_std::testing::mock_info; - use cosmwasm_std::Uint128; + use cosmwasm_std::{Addr, Uint128}; use mixnet_contract_common::ExecuteMsg; #[test] @@ -392,42 +276,12 @@ pub mod tests { .unwrap(); assert_eq!(1, updated_nonce); - _try_remove_gateway(test.deps_mut(), Addr::unchecked(sender), None).unwrap(); + try_remove_gateway(test.deps_mut(), info.clone()).unwrap(); let res = try_add_gateway(test.deps_mut(), env, info, gateway, signature); assert_eq!(res, Err(MixnetContractError::InvalidEd25519Signature)); } - #[test] - fn gateway_add_with_illegal_proxy() { - let mut test = TestSetup::new(); - let env = test.env(); - - let illegal_proxy = Addr::unchecked("not-vesting-contract"); - let vesting_contract = test.vesting_contract(); - - let owner = "alice"; - let (gateway, sig) = test.gateway_with_signature(owner, None); - - let res = try_add_gateway_on_behalf( - test.deps_mut(), - env, - mock_info(illegal_proxy.as_ref(), &good_gateway_pledge()), - gateway, - owner.to_string(), - sig, - ) - .unwrap_err(); - - assert_eq!( - res, - MixnetContractError::SenderIsNotVestingContract { - received: illegal_proxy, - vesting_contract - } - ) - } - #[test] fn gateway_remove() { let mut test = TestSetup::new(); @@ -495,7 +349,6 @@ pub mod tests { .add_message(expected_message) .add_event(new_gateway_unbonding_event( &Addr::unchecked("fred"), - &None, &tests::fixtures::good_gateway_pledge()[0], &fred_identity, )); @@ -510,33 +363,6 @@ pub mod tests { assert_eq!(&Addr::unchecked("bob"), nodes[0].owner()); } - #[test] - fn gateway_remove_with_illegal_proxy() { - let mut test = TestSetup::new(); - - let illegal_proxy = Addr::unchecked("not-vesting-contract"); - let vesting_contract = test.vesting_contract(); - - let owner = "alice"; - - test.add_dummy_gateway_with_illegal_proxy(owner, None, illegal_proxy.clone()); - - let res = try_remove_gateway_on_behalf( - test.deps_mut(), - mock_info(illegal_proxy.as_ref(), &good_gateway_pledge()), - owner.to_string(), - ) - .unwrap_err(); - - assert_eq!( - res, - MixnetContractError::SenderIsNotVestingContract { - received: illegal_proxy, - vesting_contract - } - ) - } - #[test] fn update_gateway_config() { let mut test = TestSetup::new(); @@ -561,22 +387,6 @@ pub mod tests { ); test.add_dummy_gateway(owner, None); - let vesting_contract = test.vesting_contract(); - - // attempted to remove on behalf with invalid proxy (current is `None`) - let res = try_update_gateway_config_on_behalf( - test.deps_mut(), - mock_info(vesting_contract.as_ref(), &[]), - update.clone(), - owner.to_string(), - ); - assert_eq!( - res, - Err(MixnetContractError::ProxyMismatch { - existing: "None".to_string(), - incoming: vesting_contract.into_string() - }) - ); // "normal" update succeeds let res = try_update_gateway_config(test.deps_mut(), info, update.clone()); @@ -591,39 +401,4 @@ pub mod tests { assert_eq!(bond.gateway.location, update.location); assert_eq!(bond.gateway.version, update.version); } - - #[test] - fn updating_gateway_config_with_illegal_proxy() { - let mut test = TestSetup::new(); - - let illegal_proxy = Addr::unchecked("not-vesting-contract"); - let vesting_contract = test.vesting_contract(); - - let owner = "alice"; - - test.add_dummy_gateway_with_illegal_proxy(owner, None, illegal_proxy.clone()); - let update = GatewayConfigUpdate { - host: "1.1.1.1:1234".to_string(), - mix_port: 1234, - clients_port: 1235, - location: "at home".to_string(), - version: "v1.2.3".to_string(), - }; - - let res = try_update_gateway_config_on_behalf( - test.deps_mut(), - mock_info(illegal_proxy.as_ref(), &[]), - update, - owner.to_string(), - ) - .unwrap_err(); - - assert_eq!( - res, - MixnetContractError::SenderIsNotVestingContract { - received: illegal_proxy, - vesting_contract - } - ) - } } diff --git a/contracts/mixnet/src/interval/pending_events.rs b/contracts/mixnet/src/interval/pending_events.rs index e0c5077a8f9..fa44e36ec3a 100644 --- a/contracts/mixnet/src/interval/pending_events.rs +++ b/contracts/mixnet/src/interval/pending_events.rs @@ -24,7 +24,7 @@ use crate::interval::storage; use crate::mixnodes::helpers::{cleanup_post_unbond_mixnode_storage, get_mixnode_details_by_id}; use crate::mixnodes::storage as mixnodes_storage; use crate::rewards::storage as rewards_storage; -use crate::support::helpers::{send_to_proxy_or_owner, VestingTracking}; +use crate::support::helpers::AttachSendTokens; pub(crate) trait ContractExecutableEvent { // note: the error only means a HARD error like we failed to read from storage. @@ -40,7 +40,6 @@ pub(crate) fn delegate( owner: Addr, mix_id: MixId, amount: Coin, - proxy: Option, ) -> Result { // check if the target node still exists (it might have unbonded between this event getting created // and being executed). Do note that it's absolutely possible for a mixnode to get immediately @@ -56,20 +55,9 @@ pub(crate) fn delegate( _ => { // if mixnode is no longer bonded or in the process of unbonding, return the tokens back to the // delegator; - // (read the notes regarding possible epoch progressiong halting behaviour in `maybe_add_track_undelegation_message`) - let return_tokens = send_to_proxy_or_owner(&proxy, &owner, vec![amount.clone()]); let response = Response::new() - .add_message(return_tokens) - .add_event(new_delegation_on_unbonded_node_event( - &owner, &proxy, mix_id, - )) - .maybe_add_track_vesting_undelegation_message( - deps.storage, - proxy, - owner.to_string(), - mix_id, - amount, - )?; + .send_tokens(&owner, amount.clone()) + .add_event(new_delegation_on_unbonded_node_event(&owner, mix_id)); return Ok(response); } @@ -84,7 +72,7 @@ pub(crate) fn delegate( // if there's an existing delegation, then withdraw the full reward and create a new delegation // with the sum of both - let storage_key = Delegation::generate_storage_key(mix_id, &owner, proxy.as_ref()); + let storage_key = Delegation::generate_storage_key(mix_id, &owner, None); let old_delegation = if let Some(existing_delegation) = delegations_storage::delegations().may_load(deps.storage, storage_key.clone())? { @@ -106,7 +94,6 @@ pub(crate) fn delegate( let cosmos_event = new_delegation_event( created_at, &owner, - &proxy, &new_delegation_amount, mix_id, mix_rewarding.total_unit_reward, @@ -118,7 +105,6 @@ pub(crate) fn delegate( mix_rewarding.total_unit_reward, stored_delegation_amount, env.block.height, - proxy, ); // save on reading since `.save()` would have attempted to read old data that we already have on hand @@ -138,11 +124,10 @@ pub(crate) fn undelegate( created_at: BlockHeight, owner: Addr, mix_id: MixId, - proxy: Option, ) -> Result { // see if the delegation still exists (in case of impatient user who decided to send multiple // undelegation requests in an epoch) - let storage_key = Delegation::generate_storage_key(mix_id, &owner, proxy.as_ref()); + let storage_key = Delegation::generate_storage_key(mix_id, &owner, None); let delegation = match delegations_storage::delegations().may_load(deps.storage, storage_key)? { None => return Ok(Response::default()), Some(delegation) => delegation, @@ -155,18 +140,9 @@ pub(crate) fn undelegate( let tokens_to_return = delegations::helpers::undelegate(deps.storage, delegation, mix_rewarding)?; - // (read the notes regarding possible epoch progressiong halting behaviour in `maybe_add_track_undelegation_message`) - let return_tokens = send_to_proxy_or_owner(&proxy, &owner, vec![tokens_to_return.clone()]); let response = Response::new() - .add_message(return_tokens) - .add_event(new_undelegation_event(created_at, &owner, &proxy, mix_id)) - .maybe_add_track_vesting_undelegation_message( - deps.storage, - proxy, - owner.to_string(), - mix_id, - tokens_to_return, - )?; + .send_tokens(&owner, tokens_to_return.clone()) + .add_event(new_undelegation_event(created_at, &owner, mix_id)); Ok(response) } @@ -197,25 +173,15 @@ pub(crate) fn unbond_mixnode( .rewarding_details .operator_pledge_with_reward(rewarding_denom); - let proxy = &node_details.bond_information.proxy; let owner = &node_details.bond_information.owner; - // send bonded funds (alongside all earned rewards) to the bond owner - let return_tokens = send_to_proxy_or_owner(proxy, owner, vec![tokens.clone()]); - // remove the bond and if there are no delegations left, also the rewarding information // decrement the associated layer count cleanup_post_unbond_mixnode_storage(deps.storage, env, &node_details)?; let response = Response::new() - .add_message(return_tokens) - .add_event(new_mixnode_unbonding_event(created_at, mix_id)) - .maybe_add_track_vesting_unbond_mixnode_message( - deps.storage, - proxy.clone(), - owner.clone().into_string(), - tokens, - )?; + .send_tokens(owner, tokens.clone()) + .add_event(new_mixnode_unbonding_event(created_at, mix_id)); Ok(response) } @@ -311,12 +277,8 @@ pub(crate) fn decrease_pledge( updated_bond.original_pledge.amount -= decrease_by.amount; updated_rewarding.decrease_operator_uint128(decrease_by.amount)?; - let proxy = &mix_details.bond_information.proxy; let owner = &mix_details.bond_information.owner; - // send the removed tokens back to the operator - let return_tokens = send_to_proxy_or_owner(proxy, owner, vec![decrease_by.clone()]); - // update all: bond information, rewarding details and pending pledge changes mixnodes_storage::mixnode_bonds().replace( deps.storage, @@ -328,14 +290,8 @@ pub(crate) fn decrease_pledge( mixnodes_storage::PENDING_MIXNODE_CHANGES.save(deps.storage, mix_id, &pending_changes)?; let response = Response::new() - .add_message(return_tokens) - .add_event(new_pledge_decrease_event(created_at, mix_id, &decrease_by)) - .maybe_add_track_vesting_decrease_mixnode_pledge( - deps.storage, - proxy.clone(), - owner.clone().to_string(), - decrease_by, - )?; + .send_tokens(owner, decrease_by.clone()) + .add_event(new_pledge_decrease_event(created_at, mix_id, &decrease_by)); Ok(response) } @@ -349,13 +305,11 @@ impl ContractExecutableEvent for PendingEpochEventData { owner, mix_id, amount, - proxy, - } => delegate(deps, env, self.created_at, owner, mix_id, amount, proxy), - PendingEpochEventKind::Undelegate { - owner, - mix_id, - proxy, - } => undelegate(deps, self.created_at, owner, mix_id, proxy), + .. + } => delegate(deps, env, self.created_at, owner, mix_id, amount), + PendingEpochEventKind::Undelegate { owner, mix_id, .. } => { + undelegate(deps, self.created_at, owner, mix_id) + } PendingEpochEventKind::PledgeMore { mix_id, amount } => { increase_pledge(deps, self.created_at, mix_id, amount) } @@ -472,33 +426,25 @@ impl ContractExecutableEvent for PendingIntervalEventData { #[cfg(test)] mod tests { - use std::time::Duration; - - use cosmwasm_std::Decimal; - - use mixnet_contract_common::Percent; - use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg; - + use super::*; use crate::support::tests::test_helpers; use crate::support::tests::test_helpers::{assert_decimals, TestSetup}; - - use super::*; + use cosmwasm_std::Decimal; + use mixnet_contract_common::Percent; + use std::time::Duration; // note that authorization and basic validation has already been performed for all of those // before being pushed onto the event queues #[cfg(test)] mod delegating { - use cosmwasm_std::testing::mock_info; - use cosmwasm_std::{coin, to_binary, CosmosMsg, WasmMsg}; - - use mixnet_contract_common::rewarding::helpers::truncate_reward_amount; - + use super::*; use crate::mixnodes::transactions::try_remove_mixnode; use crate::support::tests::fixtures::TEST_COIN_DENOM; use crate::support::tests::test_helpers::get_bank_send_msg; - - use super::*; + use cosmwasm_std::coin; + use cosmwasm_std::testing::mock_info; + use mixnet_contract_common::rewarding::helpers::truncate_reward_amount; #[test] fn returns_the_tokens_if_mixnode_has_unbonded() { @@ -523,7 +469,6 @@ mod tests { Addr::unchecked(owner1), mix_id, delegation_coin.clone(), - None, ) .unwrap(); @@ -549,7 +494,6 @@ mod tests { Addr::unchecked(owner2), mix_id, delegation_coin.clone(), - None, ) .unwrap(); let storage_key = @@ -588,7 +532,6 @@ mod tests { Addr::unchecked(owner1), mix_id, delegation_coin.clone(), - None, ) .unwrap(); @@ -614,7 +557,6 @@ mod tests { Addr::unchecked(owner2), mix_id, delegation_coin.clone(), - None, ) .unwrap(); let storage_key = @@ -650,7 +592,6 @@ mod tests { Addr::unchecked(owner), mix_id, delegation_coin_new, - None, ) .unwrap(); @@ -725,7 +666,6 @@ mod tests { Addr::unchecked(owner), mix_id, delegation_coin_new, - None, ) .unwrap(); @@ -797,7 +737,6 @@ mod tests { Addr::unchecked(owner), mix_id, delegation_coin.clone(), - None, ) .unwrap(); assert!(get_bank_send_msg(&res).is_none()); @@ -816,117 +755,13 @@ mod tests { Decimal::from_atomics(delegation, 0).unwrap() ) } - - #[test] - fn attaches_vesting_contract_track_message_if_tokens_are_returned() { - let mut test = TestSetup::new(); - let mix_id = test.add_dummy_mixnode("mix-owner", None); - - let delegation = 120_000_000u128; - let delegation_coin = coin(delegation, TEST_COIN_DENOM); - let owner = "delegator"; - - let env = test.env(); - unbond_mixnode(test.deps_mut(), &env, 123, mix_id).unwrap(); - - let vesting_contract = test.vesting_contract(); - - // for a fresh delegation, nothing was added to the storage either - let res_vesting = delegate( - test.deps_mut(), - &env, - 123, - Addr::unchecked(owner), - mix_id, - delegation_coin.clone(), - Some(vesting_contract.clone()), - ) - .unwrap(); - let storage_key = Delegation::generate_storage_key( - mix_id, - &Addr::unchecked(owner), - Some(vesting_contract.clone()).as_ref(), - ); - assert!(delegations_storage::delegations() - .may_load(test.deps().storage, storage_key) - .unwrap() - .is_none()); - // and all tokens are returned back to the proxy - let (receiver, sent_amount) = get_bank_send_msg(&res_vesting).unwrap(); - assert_eq!(receiver, vesting_contract.as_str()); - assert_eq!(sent_amount[0], delegation_coin); - - // and we get appropriate track message - let mut found_track = true; - for msg in &res_vesting.messages { - if let CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr, - msg, - funds, - }) = &msg.msg - { - found_track = true; - assert_eq!(contract_addr, vesting_contract.as_str()); - let expected_msg = to_binary(&VestingContractExecuteMsg::TrackUndelegation { - owner: owner.to_string(), - mix_id, - amount: delegation_coin.clone(), - }) - .unwrap(); - assert_eq!(&expected_msg, msg); - assert!(funds.is_empty()) - } - } - assert!(found_track); - } - - #[test] - fn returns_error_for_illegal_proxy() { - let mut test = TestSetup::new(); - let mix_id = test.add_dummy_mixnode("mix-owner", None); - - let delegation = 120_000_000u128; - let delegation_coin = coin(delegation, TEST_COIN_DENOM); - let owner = "delegator"; - let dummy_proxy = Addr::unchecked("not-vesting-contract"); - - let env = test.env(); - unbond_mixnode(test.deps_mut(), &env, 123, mix_id).unwrap(); - - let vesting_contract = test.vesting_contract(); - - // try to add illegal delegation (with invalid proxy) - let res_other_proxy = delegate( - test.deps_mut(), - &env, - 123, - Addr::unchecked(owner), - mix_id, - delegation_coin, - Some(dummy_proxy.clone()), - ) - .unwrap_err(); - - assert_eq!( - res_other_proxy, - MixnetContractError::ProxyIsNotVestingContract { - received: dummy_proxy, - vesting_contract, - } - ); - } } #[cfg(test)] mod undelegating { - use cosmwasm_std::{coin, to_binary, CosmosMsg, WasmMsg}; - - use mixnet_contract_common::rewarding::helpers::truncate_reward_amount; - - use crate::support::tests::fixtures::TEST_COIN_DENOM; - use crate::support::tests::test_helpers::get_bank_send_msg; - use super::*; + use crate::support::tests::test_helpers::get_bank_send_msg; + use mixnet_contract_common::rewarding::helpers::truncate_reward_amount; #[test] fn doesnt_return_any_tokens_if_it_doesnt_exist() { @@ -935,7 +770,7 @@ mod tests { let owner = Addr::unchecked("delegator"); - let res = undelegate(test.deps_mut(), 123, owner, mix_id, None).unwrap(); + let res = undelegate(test.deps_mut(), 123, owner, mix_id).unwrap(); assert!(get_bank_send_msg(&res).is_none()); } @@ -950,7 +785,7 @@ mod tests { // this should never happen in actual code, but if we manually messed something up, // lets make sure this throws an error rewards_storage::MIXNODE_REWARDING.remove(test.deps_mut().storage, mix_id); - let res = undelegate(test.deps_mut(), 123, owner, mix_id, None); + let res = undelegate(test.deps_mut(), 123, owner, mix_id); assert!(matches!( res, Err(MixnetContractError::InconsistentState { .. }) @@ -996,8 +831,7 @@ mod tests { let expected_return = delegation + truncated_reward.u128(); - let res = - undelegate(test.deps_mut(), 123, Addr::unchecked(owner), mix_id, None).unwrap(); + let res = undelegate(test.deps_mut(), 123, Addr::unchecked(owner), mix_id).unwrap(); let (receiver, sent_amount) = get_bank_send_msg(&res).unwrap(); assert_eq!(receiver, owner); assert_eq!(sent_amount[0].amount.u128(), expected_return); @@ -1015,116 +849,18 @@ mod tests { assert!(rewarding.delegates.is_zero()); assert_eq!(rewarding.unique_delegations, 0); } - - #[test] - fn attaches_vesting_contract_track_message_if_tokens_are_returned() { - let mut test = TestSetup::new(); - let mix_id = test.add_dummy_mixnode("mix-owner", None); - - let delegation = 120_000_000u128; - let delegation_coin = coin(delegation, TEST_COIN_DENOM); - let owner = "delegator"; - - let vesting_contract = test.vesting_contract(); - - test.add_immediate_delegation_with_legal_proxy(owner, delegation, mix_id); - - let res_vesting = undelegate( - test.deps_mut(), - 123, - Addr::unchecked(owner), - mix_id, - Some(vesting_contract.clone()), - ) - .unwrap(); - let storage_key = Delegation::generate_storage_key( - mix_id, - &Addr::unchecked(owner), - Some(vesting_contract.clone()).as_ref(), - ); - assert!(delegations_storage::delegations() - .may_load(test.deps().storage, storage_key) - .unwrap() - .is_none()); - - // and all tokens are returned back to the proxy - let (receiver, sent_amount) = get_bank_send_msg(&res_vesting).unwrap(); - assert_eq!(receiver, vesting_contract.as_str()); - assert_eq!(sent_amount[0], delegation_coin); - - // and we get appropriate track message - let mut found_track = true; - for msg in &res_vesting.messages { - if let CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr, - msg, - funds, - }) = &msg.msg - { - found_track = true; - assert_eq!(contract_addr, vesting_contract.as_str()); - let expected_msg = to_binary(&VestingContractExecuteMsg::TrackUndelegation { - owner: owner.to_string(), - mix_id, - amount: delegation_coin.clone(), - }) - .unwrap(); - assert_eq!(&expected_msg, msg); - assert!(funds.is_empty()) - } - } - assert!(found_track); - } - - #[test] - fn returns_error_for_illegal_proxy() { - let mut test = TestSetup::new(); - let mix_id = test.add_dummy_mixnode("mix-owner", None); - - let delegation = 120_000_000u128; - let owner = "delegator1"; - - let vesting_contract = test.vesting_contract(); - let dummy_proxy = Addr::unchecked("not-vesting-contract"); - - test.add_immediate_delegation_with_illegal_proxy( - owner, - delegation, - mix_id, - dummy_proxy.clone(), - ); - - let res_other_proxy = undelegate( - test.deps_mut(), - 123, - Addr::unchecked(owner), - mix_id, - Some(dummy_proxy.clone()), - ) - .unwrap_err(); - assert_eq!( - res_other_proxy, - MixnetContractError::ProxyIsNotVestingContract { - received: dummy_proxy, - vesting_contract, - } - ); - } } #[cfg(test)] mod mixnode_unbonding { - use cosmwasm_std::{coin, to_binary, CosmosMsg, Uint128, WasmMsg}; - - use mixnet_contract_common::mixnode::{PendingMixNodeChanges, UnbondedMixnode}; - use mixnet_contract_common::rewarding::helpers::truncate_reward_amount; - + use super::*; use crate::mixnodes::storage as mixnodes_storage; - use crate::mixnodes::transactions::{_try_decrease_pledge, _try_increase_pledge}; - use crate::support::tests::fixtures::TEST_COIN_DENOM; + use crate::mixnodes::transactions::{try_decrease_pledge, try_increase_pledge}; use crate::support::tests::test_helpers::get_bank_send_msg; - - use super::*; + use cosmwasm_std::testing::mock_info; + use cosmwasm_std::Uint128; + use mixnet_contract_common::mixnode::{PendingMixNodeChanges, UnbondedMixnode}; + use mixnet_contract_common::rewarding::helpers::truncate_reward_amount; #[test] fn returns_hard_error_if_mixnode_doesnt_exist() { @@ -1150,12 +886,10 @@ mod tests { let pledge = Uint128::new(250_000_000); let mix_id = test.add_dummy_mixnode(owner, Some(pledge)); - _try_increase_pledge( + try_increase_pledge( test.deps_mut(), env.clone(), - change.clone(), - Addr::unchecked(owner), - None, + mock_info(owner, &change.clone()), ) .unwrap(); @@ -1170,12 +904,11 @@ mod tests { let pledge = Uint128::new(250_000_000); let mix_id = test.add_dummy_mixnode(owner, Some(pledge)); - _try_decrease_pledge( + try_decrease_pledge( test.deps_mut(), env.clone(), + mock_info(owner, &[]), change[0].clone(), - Addr::unchecked(owner), - None, ) .unwrap(); @@ -1263,79 +996,6 @@ mod tests { 0 ) } - - #[test] - fn attaches_vesting_contract_track_message_if_tokens_are_returned() { - let mut test = TestSetup::new(); - - let vesting_contract = test.vesting_contract(); - - let pledge = Uint128::new(250_000_000); - let pledge_coin = coin(250_000_000, TEST_COIN_DENOM); - let owner = "mix-owner1"; - let mix_id_vesting = test.add_dummy_mixnode_with_legal_proxy(owner, Some(pledge)); - - let env = test.env(); - let res = unbond_mixnode(test.deps_mut(), &env, 123, mix_id_vesting).unwrap(); - - assert!(mixnodes_storage::mixnode_bonds() - .may_load(test.deps().storage, mix_id_vesting) - .unwrap() - .is_none()); - - // and all tokens are returned back to the proxy - let (receiver, sent_amount) = get_bank_send_msg(&res).unwrap(); - assert_eq!(receiver, vesting_contract.as_str()); - assert_eq!(sent_amount[0], pledge_coin); - - // and we get appropriate track message - let mut found_track = true; - for msg in &res.messages { - if let CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr, - msg, - funds, - }) = &msg.msg - { - found_track = true; - assert_eq!(contract_addr, vesting_contract.as_str()); - let expected_msg = to_binary(&VestingContractExecuteMsg::TrackUnbondMixnode { - owner: owner.to_string(), - amount: pledge_coin.clone(), - }) - .unwrap(); - assert_eq!(&expected_msg, msg); - assert!(funds.is_empty()) - } - } - assert!(found_track); - } - - #[test] - fn returns_error_for_illegal_proxy() { - let mut test = TestSetup::new(); - - let dummy_proxy = Addr::unchecked("not-vesting-contract"); - let env = test.env(); - - let vesting_contract = test.vesting_contract(); - let owner = "mix-owner"; - let pledge = Uint128::new(250_000_000); - - let mix_id_illegal_proxy = - test.add_dummy_mixnode_with_illegal_proxy(owner, Some(pledge), dummy_proxy.clone()); - - // this is the halting issue that should have never occurred - let res_other_proxy = - unbond_mixnode(test.deps_mut(), &env, 123, mix_id_illegal_proxy).unwrap_err(); - assert_eq!( - res_other_proxy, - MixnetContractError::ProxyIsNotVestingContract { - received: dummy_proxy, - vesting_contract, - } - ); - } } #[cfg(test)] @@ -1615,11 +1275,9 @@ mod tests { #[cfg(test)] mod decreasing_pledge { - use cosmwasm_std::{to_binary, BankMsg, CosmosMsg, Uint128, WasmMsg}; - - use mixnet_contract_common::rewarding::helpers::truncate_reward_amount; - use super::*; + use cosmwasm_std::{BankMsg, CosmosMsg, Uint128}; + use mixnet_contract_common::rewarding::helpers::truncate_reward_amount; #[test] fn returns_hard_error_if_mixnode_doesnt_exist() { @@ -1699,64 +1357,6 @@ mod tests { ) } - #[test] - fn returns_tokens_back_to_the_proxy_if_bonded_with_vesting() { - let mut test = TestSetup::new(); - let owner = "mix-owner"; - let mix_id = test.add_dummy_mixnode_with_legal_proxy(owner, None); - test.set_pending_pledge_change(mix_id, None); - - let vesting_contract = test.vesting_contract(); - - let amount = test.coin(12345); - let res = decrease_pledge(test.deps_mut(), 123, mix_id, amount.clone()).unwrap(); - - assert_eq!(res.messages.len(), 2); - assert_eq!( - res.messages[0].msg, - CosmosMsg::Bank(BankMsg::Send { - to_address: vesting_contract.to_string(), - amount: vec![amount], - }) - ) - } - - #[test] - fn attaches_vesting_track_message() { - let mut test = TestSetup::new(); - let mix_id_no_proxy = test.add_dummy_mixnode("mix-owner1", None); - test.set_pending_pledge_change(mix_id_no_proxy, None); - - let mix_id_proxy = test.add_dummy_mixnode_with_legal_proxy("mix-owner2", None); - test.set_pending_pledge_change(mix_id_proxy, None); - - let vesting_contract = test.vesting_contract(); - - let amount = test.coin(12345); - let res_no_proxy = - decrease_pledge(test.deps_mut(), 123, mix_id_no_proxy, amount.clone()).unwrap(); - - // nothing was attached (apart from bank message tested in `returns_tokens_back_to_the_owner`) - // because it wasn't done with proxy! - assert_eq!(res_no_proxy.messages.len(), 1); - - let res_proxy = - decrease_pledge(test.deps_mut(), 123, mix_id_proxy, amount.clone()).unwrap(); - assert_eq!(res_proxy.messages.len(), 2); - assert_eq!( - res_proxy.messages[1].msg, - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: vesting_contract.to_string(), - msg: to_binary(&VestingContractExecuteMsg::TrackDecreasePledge { - owner: "mix-owner2".to_string(), - amount, - }) - .unwrap(), - funds: vec![], - }) - ); - } - #[test] fn without_any_events_in_between_is_equivalent_to_pledging_the_same_amount_immediately() { let mut test = TestSetup::new(); diff --git a/contracts/mixnet/src/interval/queries.rs b/contracts/mixnet/src/interval/queries.rs index d0b480848f4..423d9a90ae0 100644 --- a/contracts/mixnet/src/interval/queries.rs +++ b/contracts/mixnet/src/interval/queries.rs @@ -159,11 +159,8 @@ mod tests { } fn push_dummy_epoch_action(test: &mut TestSetup) { - let dummy_action = PendingEpochEventKind::Undelegate { - owner: Addr::unchecked("foomp"), - mix_id: test.rng.next_u32(), - proxy: None, - }; + let dummy_action = + PendingEpochEventKind::new_undelegate(Addr::unchecked("foomp"), test.rng.next_u32()); let env = test.env(); storage::push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap(); } @@ -571,11 +568,8 @@ mod tests { ); // it exists - let dummy_action = PendingEpochEventKind::Undelegate { - owner: Addr::unchecked("foomp"), - mix_id: test.rng.next_u32(), - proxy: None, - }; + let dummy_action = + PendingEpochEventKind::new_undelegate(Addr::unchecked("foomp"), test.rng.next_u32()); let env = test.env(); storage::push_new_epoch_event(test.deps_mut().storage, &env, dummy_action.clone()).unwrap(); let expected = PendingEpochEventResponse { diff --git a/contracts/mixnet/src/interval/storage.rs b/contracts/mixnet/src/interval/storage.rs index 24666db431a..33eddd4d4e8 100644 --- a/contracts/mixnet/src/interval/storage.rs +++ b/contracts/mixnet/src/interval/storage.rs @@ -222,11 +222,10 @@ mod tests { let env = test.env(); for _ in 0..500 { - let dummy_action = PendingEpochEventKind::Undelegate { - owner: Addr::unchecked("foomp"), - mix_id: test.rng.next_u32(), - proxy: None, - }; + let dummy_action = PendingEpochEventKind::new_undelegate( + Addr::unchecked("foomp"), + test.rng.next_u32(), + ); let id = push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap(); let expected = EPOCH_EVENT_ID_COUNTER.load(test.deps().storage).unwrap(); assert_eq!(expected, id); @@ -235,11 +234,10 @@ mod tests { test.execute_all_pending_events(); for _ in 0..10 { - let dummy_action = PendingEpochEventKind::Undelegate { - owner: Addr::unchecked("foomp"), - mix_id: test.rng.next_u32(), - proxy: None, - }; + let dummy_action = PendingEpochEventKind::new_undelegate( + Addr::unchecked("foomp"), + test.rng.next_u32(), + ); let id = push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap(); let expected = EPOCH_EVENT_ID_COUNTER.load(test.deps().storage).unwrap(); assert_eq!(expected, id); diff --git a/contracts/mixnet/src/interval/transactions.rs b/contracts/mixnet/src/interval/transactions.rs index 0eae4fed7b7..349545984f8 100644 --- a/contracts/mixnet/src/interval/transactions.rs +++ b/contracts/mixnet/src/interval/transactions.rs @@ -373,18 +373,14 @@ mod tests { use crate::support::tests::test_helpers::TestSetup; use cosmwasm_std::Addr; use mixnet_contract_common::pending_events::PendingEpochEventKind; - use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg; fn push_n_dummy_epoch_actions(test: &mut TestSetup, n: usize) { // if you attempt to undelegate non-existent delegation, // it will return an empty response, but will not fail let env = test.env(); for i in 0..n { - let dummy_action = PendingEpochEventKind::Undelegate { - owner: Addr::unchecked("foomp"), - mix_id: i as MixId, - proxy: None, - }; + let dummy_action = + PendingEpochEventKind::new_undelegate(Addr::unchecked("foomp"), i as MixId); storage::push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap(); } } @@ -406,7 +402,7 @@ mod tests { mod performing_pending_epoch_actions { use super::*; use crate::support::tests::fixtures::TEST_COIN_DENOM; - use cosmwasm_std::{coin, coins, wasm_execute, BankMsg, Empty, SubMsg}; + use cosmwasm_std::{coin, coins, BankMsg, Empty, SubMsg}; use mixnet_contract_common::events::{ new_active_set_update_event, new_delegation_on_unbonded_node_event, new_undelegation_event, @@ -495,7 +491,6 @@ mod tests { #[test] fn catches_all_events_and_messages_from_executed_actions() { let mut test = TestSetup::new(); - let vesting_contract = test.vesting_contract(); let env = test.env(); let legit_mix = test.add_dummy_mixnode("mix-owner", None); @@ -509,17 +504,15 @@ mod tests { // delegate to node that doesn't exist, // we expect to receive BankMsg with tokens being returned, // and event regarding delegation - let non_existent_delegation = PendingEpochEventKind::Delegate { - owner: Addr::unchecked("foomp"), - mix_id: 123, - amount: coin(123, TEST_COIN_DENOM), - proxy: None, - }; + let non_existent_delegation = PendingEpochEventKind::new_delegate( + Addr::unchecked("foomp"), + 123, + coin(123, TEST_COIN_DENOM), + ); storage::push_new_epoch_event(test.deps_mut().storage, &env, non_existent_delegation) .unwrap(); expected_events.push(new_delegation_on_unbonded_node_event( &Addr::unchecked("foomp"), - &None, 123, )); expected_messages.push(SubMsg::new(BankMsg::Send { @@ -527,33 +520,6 @@ mod tests { amount: coins(123, TEST_COIN_DENOM), })); - // delegation to node that doesn't exist with vesting contract - // we expect the same as above PLUS TrackUndelegation message - let non_existent_delegation = PendingEpochEventKind::Delegate { - owner: Addr::unchecked("foomp2"), - mix_id: 123, - amount: coin(123, TEST_COIN_DENOM), - proxy: Some(vesting_contract.clone()), - }; - storage::push_new_epoch_event(test.deps_mut().storage, &env, non_existent_delegation) - .unwrap(); - expected_events.push(new_delegation_on_unbonded_node_event( - &Addr::unchecked("foomp2"), - &Some(vesting_contract.clone()), - 123, - )); - expected_messages.push(SubMsg::new(BankMsg::Send { - to_address: vesting_contract.clone().into_string(), - amount: coins(123, TEST_COIN_DENOM), - })); - let msg = VestingContractExecuteMsg::TrackUndelegation { - owner: "foomp2".to_string(), - mix_id: 123, - amount: coin(123, TEST_COIN_DENOM), - }; - let track_undelegate_message = wasm_execute(vesting_contract, &msg, vec![]).unwrap(); - expected_messages.push(SubMsg::new(track_undelegate_message)); - // updating active set should only emit events and no cosmos messages let action_with_event = PendingEpochEventKind::UpdateActiveSetSize { new_size: 50 }; storage::push_new_epoch_event(test.deps_mut().storage, &env, action_with_event) @@ -561,16 +527,12 @@ mod tests { expected_events.push(new_active_set_update_event(env.block.height, 50)); // undelegation just returns tokens and emits event - let legit_undelegate = PendingEpochEventKind::Undelegate { - owner: delegator.clone(), - mix_id: legit_mix, - proxy: None, - }; + let legit_undelegate = + PendingEpochEventKind::new_undelegate(delegator.clone(), legit_mix); storage::push_new_epoch_event(test.deps_mut().storage, &env, legit_undelegate).unwrap(); expected_events.push(new_undelegation_event( env.block.height, &delegator, - &None, legit_mix, )); expected_messages.push(SubMsg::new(BankMsg::Send { @@ -583,9 +545,9 @@ mod tests { let mut expected = Response::new().add_events(expected_events); expected.messages = expected_messages; assert_eq!(res, expected); - assert_eq!(executed, 4); + assert_eq!(executed, 3); assert_eq!( - 4, + 3, storage::LAST_PROCESSED_EPOCH_EVENT .load(test.deps().storage) .unwrap() @@ -1330,17 +1292,15 @@ mod tests { let mut expected_messages: Vec> = Vec::new(); // epoch event - let non_existent_delegation = PendingEpochEventKind::Delegate { - owner: Addr::unchecked("foomp"), - mix_id: 123, - amount: coin(123, TEST_COIN_DENOM), - proxy: None, - }; + let non_existent_delegation = PendingEpochEventKind::new_delegate( + Addr::unchecked("foomp"), + 123, + coin(123, TEST_COIN_DENOM), + ); storage::push_new_epoch_event(test.deps_mut().storage, &env, non_existent_delegation) .unwrap(); expected_events.push(new_delegation_on_unbonded_node_event( &Addr::unchecked("foomp"), - &None, 123, )); expected_messages.push(SubMsg::new(BankMsg::Send { diff --git a/contracts/mixnet/src/lib.rs b/contracts/mixnet/src/lib.rs index 80e6822f896..985cffda57b 100644 --- a/contracts/mixnet/src/lib.rs +++ b/contracts/mixnet/src/lib.rs @@ -19,3 +19,4 @@ mod support; #[cfg(feature = "contract-testing")] mod testing; +mod vesting_migration; diff --git a/contracts/mixnet/src/mixnodes/helpers.rs b/contracts/mixnet/src/mixnodes/helpers.rs index 2650283b051..b8dd15ae6e4 100644 --- a/contracts/mixnet/src/mixnodes/helpers.rs +++ b/contracts/mixnet/src/mixnodes/helpers.rs @@ -97,7 +97,6 @@ pub(crate) fn save_new_mixnode( mixnode: MixNode, cost_params: MixNodeCostParams, owner: Addr, - proxy: Option, pledge: Coin, ) -> Result<(MixId, Layer), MixnetContractError> { let layer = assign_layer(storage)?; @@ -105,15 +104,7 @@ pub(crate) fn save_new_mixnode( let current_epoch = interval_storage::current_interval(storage)?.current_epoch_absolute_id(); let mixnode_rewarding = MixNodeRewarding::initialise_new(cost_params, &pledge, current_epoch)?; - let mixnode_bond = MixNodeBond::new( - mix_id, - owner, - pledge, - layer, - mixnode, - proxy, - env.block.height, - ); + let mixnode_bond = MixNodeBond::new(mix_id, owner, pledge, layer, mixnode, env.block.height); // save mixnode bond data // note that this implicitly checks for uniqueness on identity key, sphinx key and owner @@ -411,7 +402,6 @@ pub(crate) mod tests { mixnode, cost_params.clone(), owner.clone(), - None, pledge.clone(), ) .unwrap(); @@ -444,7 +434,6 @@ pub(crate) mod tests { mixnode, cost_params.clone(), Addr::unchecked("different-owner"), - None, pledge.clone(), ); assert!(res.is_err()); @@ -457,7 +446,6 @@ pub(crate) mod tests { mixnode, cost_params.clone(), owner, - None, pledge.clone(), ); assert!(res.is_err()); @@ -471,7 +459,6 @@ pub(crate) mod tests { mixnode, cost_params, Addr::unchecked("different-owner"), - None, pledge, ); assert!(res.is_err()); diff --git a/contracts/mixnet/src/mixnodes/signature_helpers.rs b/contracts/mixnet/src/mixnodes/signature_helpers.rs index 2ccec88d810..975bb556693 100644 --- a/contracts/mixnet/src/mixnodes/signature_helpers.rs +++ b/contracts/mixnet/src/mixnodes/signature_helpers.rs @@ -12,7 +12,6 @@ use nym_contracts_common::signing::Verifier; pub(crate) fn verify_mixnode_bonding_signature( deps: Deps<'_>, sender: Addr, - proxy: Option, pledge: Coin, mixnode: MixNode, cost_params: MixNodeCostParams, @@ -23,8 +22,7 @@ pub(crate) fn verify_mixnode_bonding_signature( // reconstruct the payload let nonce = signing_storage::get_signing_nonce(deps.storage, sender.clone())?; - let msg = - construct_mixnode_bonding_sign_payload(nonce, sender, proxy, pledge, mixnode, cost_params); + let msg = construct_mixnode_bonding_sign_payload(nonce, sender, pledge, mixnode, cost_params); if deps.api.verify_message(msg, signature, &public_key)? { Ok(()) diff --git a/contracts/mixnet/src/mixnodes/transactions.rs b/contracts/mixnet/src/mixnodes/transactions.rs index 032ef29bc92..703769365e9 100644 --- a/contracts/mixnet/src/mixnodes/transactions.rs +++ b/contracts/mixnet/src/mixnodes/transactions.rs @@ -1,8 +1,7 @@ -// Copyright 2021-2023 - Nym Technologies SA +// Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use cosmwasm_std::{coin, Addr, Coin, DepsMut, Env, MessageInfo, Response, Storage}; - +use cosmwasm_std::{coin, Coin, DepsMut, Env, MessageInfo, Response, Storage}; use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::events::{ new_mixnode_bonding_event, new_mixnode_config_update_event, @@ -25,8 +24,7 @@ use crate::mixnodes::signature_helpers::verify_mixnode_bonding_signature; use crate::signing::storage as signing_storage; use crate::support::helpers::{ ensure_bonded, ensure_epoch_in_progress_state, ensure_is_authorized, ensure_no_existing_bond, - ensure_no_pending_pledge_changes, ensure_proxy_match, ensure_sent_by_vesting_contract, - validate_pledge, + ensure_no_pending_pledge_changes, validate_pledge, }; use super::storage; @@ -61,74 +59,24 @@ pub fn assign_mixnode_layer( Ok(Response::default()) } -pub fn try_add_mixnode( - deps: DepsMut<'_>, - env: Env, - info: MessageInfo, - mix_node: MixNode, - cost_params: MixNodeCostParams, - owner_signature: MessageSignature, -) -> Result { - _try_add_mixnode( - deps, - env, - mix_node, - cost_params, - info.funds, - info.sender, - owner_signature, - None, - ) -} - -pub fn try_add_mixnode_on_behalf( - deps: DepsMut<'_>, - env: Env, - info: MessageInfo, - mix_node: MixNode, - cost_params: MixNodeCostParams, - owner: String, - owner_signature: MessageSignature, -) -> Result { - ensure_sent_by_vesting_contract(&info, deps.storage)?; - - let proxy = info.sender; - let owner = deps.api.addr_validate(&owner)?; - _try_add_mixnode( - deps, - env, - mix_node, - cost_params, - info.funds, - owner, - owner_signature, - Some(proxy), - ) -} - -// I'm not entirely sure how to deal with this warning at the current moment -// // TODO: perhaps also require the user to explicitly provide what it thinks is the current nonce // so that we could return a better error message if it doesn't match? -#[allow(clippy::too_many_arguments)] -fn _try_add_mixnode( +pub(crate) fn try_add_mixnode( deps: DepsMut<'_>, env: Env, + info: MessageInfo, mixnode: MixNode, cost_params: MixNodeCostParams, - pledge: Vec, - owner: Addr, owner_signature: MessageSignature, - proxy: Option, ) -> Result { // check if the pledge contains any funds of the appropriate denomination let minimum_pledge = mixnet_params_storage::minimum_mixnode_pledge(deps.storage)?; - let pledge = validate_pledge(pledge, minimum_pledge)?; + let pledge = validate_pledge(info.funds, minimum_pledge)?; // if the client has an active bonded mixnode or gateway, don't allow bonding // note that this has to be done explicitly as `UniqueIndex` constraint would not protect us // against attempting to use different node types (i.e. gateways and mixnodes) - ensure_no_existing_bond(&owner, deps.storage)?; + ensure_no_existing_bond(&info.sender, deps.storage)?; // there's no need to explicitly check whether there already exists mixnode with the same // identity or sphinx keys as this is going to be done implicitly when attempting to save @@ -137,8 +85,7 @@ fn _try_add_mixnode( // check if this sender actually owns the mixnode by checking the signature verify_mixnode_bonding_signature( deps.as_ref(), - owner.clone(), - proxy.clone(), + info.sender.clone(), pledge.clone(), mixnode.clone(), cost_params.clone(), @@ -146,7 +93,7 @@ fn _try_add_mixnode( )?; // update the signing nonce associated with this sender so that the future signature would be made on the new value - signing_storage::increment_signing_nonce(deps.storage, owner.clone())?; + signing_storage::increment_signing_nonce(deps.storage, info.sender.clone())?; let node_identity = mixnode.identity_key.clone(); let (node_id, layer) = save_new_mixnode( @@ -154,14 +101,12 @@ fn _try_add_mixnode( env, mixnode, cost_params, - owner.clone(), - proxy.clone(), + info.sender.clone(), pledge.clone(), )?; Ok(Response::new().add_event(new_mixnode_bonding_event( - &owner, - &proxy, + &info.sender, &pledge, &node_identity, node_id, @@ -174,43 +119,19 @@ pub fn try_increase_pledge( env: Env, info: MessageInfo, ) -> Result { - _try_increase_pledge(deps, env, info.funds, info.sender, None) -} - -pub fn try_increase_pledge_on_behalf( - deps: DepsMut<'_>, - env: Env, - info: MessageInfo, - owner: String, -) -> Result { - ensure_sent_by_vesting_contract(&info, deps.storage)?; - - let proxy = info.sender; - let owner = deps.api.addr_validate(&owner)?; - _try_increase_pledge(deps, env, info.funds, owner, Some(proxy)) -} - -pub fn _try_increase_pledge( - deps: DepsMut<'_>, - env: Env, - increase: Vec, - owner: Addr, - proxy: Option, -) -> Result { - let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())? - .ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })?; + let mix_details = get_mixnode_details_by_owner(deps.storage, info.sender.clone())? + .ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner: info.sender })?; let mut pending_changes = mix_details.pending_changes; let mix_id = mix_details.mix_id(); // increasing pledge is only allowed if the epoch is currently not in the process of being advanced ensure_epoch_in_progress_state(deps.storage)?; - ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?; ensure_bonded(&mix_details.bond_information)?; ensure_no_pending_pledge_changes(&pending_changes)?; let rewarding_denom = rewarding_denom(deps.storage)?; - let pledge_increase = validate_pledge(increase, coin(1, rewarding_denom))?; + let pledge_increase = validate_pledge(info.funds, coin(1, rewarding_denom))?; let cosmos_event = new_pending_pledge_increase_event(mix_id, &pledge_increase); @@ -232,39 +153,14 @@ pub fn try_decrease_pledge( info: MessageInfo, decrease_by: Coin, ) -> Result { - _try_decrease_pledge(deps, env, decrease_by, info.sender, None) -} - -pub fn try_decrease_pledge_on_behalf( - deps: DepsMut<'_>, - env: Env, - info: MessageInfo, - decrease_by: Coin, - owner: String, -) -> Result { - ensure_sent_by_vesting_contract(&info, deps.storage)?; - - let proxy = info.sender; - let owner = deps.api.addr_validate(&owner)?; - _try_decrease_pledge(deps, env, decrease_by, owner, Some(proxy)) -} - -pub fn _try_decrease_pledge( - deps: DepsMut<'_>, - env: Env, - decrease_by: Coin, - owner: Addr, - proxy: Option, -) -> Result { - let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())? - .ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })?; + let mix_details = get_mixnode_details_by_owner(deps.storage, info.sender.clone())? + .ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner: info.sender })?; let mut pending_changes = mix_details.pending_changes; let mix_id = mix_details.mix_id(); // decreasing pledge is only allowed if the epoch is currently not in the process of being advanced ensure_epoch_in_progress_state(deps.storage)?; - ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?; ensure_bonded(&mix_details.bond_information)?; ensure_no_pending_pledge_changes(&pending_changes)?; @@ -312,34 +208,12 @@ pub fn _try_decrease_pledge( Ok(Response::new().add_event(cosmos_event)) } -pub fn try_remove_mixnode_on_behalf( - deps: DepsMut<'_>, - env: Env, - info: MessageInfo, - owner: String, -) -> Result { - ensure_sent_by_vesting_contract(&info, deps.storage)?; - - let proxy = info.sender; - let owner = deps.api.addr_validate(&owner)?; - _try_remove_mixnode(deps, env, owner, Some(proxy)) -} - -pub fn try_remove_mixnode( +pub(crate) fn try_remove_mixnode( deps: DepsMut<'_>, env: Env, info: MessageInfo, ) -> Result { - _try_remove_mixnode(deps, env, info.sender, None) -} - -pub(crate) fn _try_remove_mixnode( - deps: DepsMut<'_>, - env: Env, - owner: Addr, - proxy: Option, -) -> Result { - let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?; + let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?; let pending_changes = storage::PENDING_MIXNODE_CHANGES .may_load(deps.storage, existing_bond.mix_id)? .unwrap_or_default(); @@ -348,15 +222,12 @@ pub(crate) fn _try_remove_mixnode( ensure_epoch_in_progress_state(deps.storage)?; // see if the proxy matches - ensure_proxy_match(&proxy, &existing_bond.proxy)?; ensure_bonded(&existing_bond)?; // if there are any pending requests to change the pledge, wait for them to resolve before allowing the unbonding ensure_no_pending_pledge_changes(&pending_changes)?; // set `is_unbonding` field - // clippy beta 1.70.0-beta.1 false positive - #[allow(clippy::redundant_clone)] let mut updated_bond = existing_bond.clone(); updated_bond.is_unbonding = true; storage::mixnode_bonds().replace( @@ -375,7 +246,6 @@ pub(crate) fn _try_remove_mixnode( Ok( Response::new().add_event(new_pending_mixnode_unbonding_event( &existing_bond.owner, - &existing_bond.proxy, existing_bond.identity(), existing_bond.mix_id, )), @@ -387,39 +257,13 @@ pub(crate) fn try_update_mixnode_config( info: MessageInfo, new_config: MixNodeConfigUpdate, ) -> Result { - let owner = info.sender; - _try_update_mixnode_config(deps, new_config, owner, None) -} - -pub(crate) fn try_update_mixnode_config_on_behalf( - deps: DepsMut<'_>, - info: MessageInfo, - new_config: MixNodeConfigUpdate, - owner: String, -) -> Result { - ensure_sent_by_vesting_contract(&info, deps.storage)?; - - let owner = deps.api.addr_validate(&owner)?; - let proxy = info.sender; - _try_update_mixnode_config(deps, new_config, owner, Some(proxy)) -} - -pub(crate) fn _try_update_mixnode_config( - deps: DepsMut<'_>, - new_config: MixNodeConfigUpdate, - owner: Addr, - proxy: Option, -) -> Result { - let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?; + let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?; ensure_bonded(&existing_bond)?; - ensure_proxy_match(&proxy, &existing_bond.proxy)?; let cfg_update_event = - new_mixnode_config_update_event(existing_bond.mix_id, &owner, &proxy, &new_config); + new_mixnode_config_update_event(existing_bond.mix_id, &info.sender, &new_config); - // clippy beta 1.70.0-beta.1 false positive - #[allow(clippy::redundant_clone)] let mut updated_bond = existing_bond.clone(); updated_bond.mix_node.host = new_config.host; updated_bond.mix_node.mix_port = new_config.mix_port; @@ -442,45 +286,18 @@ pub(crate) fn try_update_mixnode_cost_params( env: Env, info: MessageInfo, new_costs: MixNodeCostParams, -) -> Result { - let owner = info.sender; - _try_update_mixnode_cost_params(deps, env, new_costs, owner, None) -} - -pub(crate) fn try_update_mixnode_cost_params_on_behalf( - deps: DepsMut, - env: Env, - info: MessageInfo, - new_costs: MixNodeCostParams, - owner: String, -) -> Result { - ensure_sent_by_vesting_contract(&info, deps.storage)?; - - let owner = deps.api.addr_validate(&owner)?; - let proxy = info.sender; - _try_update_mixnode_cost_params(deps, env, new_costs, owner, Some(proxy)) -} - -pub(crate) fn _try_update_mixnode_cost_params( - deps: DepsMut, - env: Env, - new_costs: MixNodeCostParams, - owner: Addr, - proxy: Option, ) -> Result { // see if the node still exists - let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?; + let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?; // changing cost params is only allowed if the epoch is currently not in the process of being advanced ensure_epoch_in_progress_state(deps.storage)?; - ensure_proxy_match(&proxy, &existing_bond.proxy)?; ensure_bonded(&existing_bond)?; let cosmos_event = new_mixnode_pending_cost_params_update_event( existing_bond.mix_id, - &owner, - &proxy, + &info.sender, &new_costs, ); @@ -497,7 +314,7 @@ pub(crate) fn _try_update_mixnode_cost_params( #[cfg(test)] pub mod tests { use cosmwasm_std::testing::mock_info; - use cosmwasm_std::{Order, StdResult, Uint128}; + use cosmwasm_std::{Addr, Order, StdResult, Uint128}; use mixnet_contract_common::mixnode::PendingMixNodeChanges; use mixnet_contract_common::{EpochState, EpochStatus, ExecuteMsg, LayerDistribution, Percent}; @@ -680,38 +497,6 @@ pub mod tests { assert_eq!(res, Err(MixnetContractError::InvalidEd25519Signature)); } - #[test] - fn mixnode_add_with_illegal_proxy() { - let mut test = TestSetup::new(); - let env = test.env(); - - let illegal_proxy = Addr::unchecked("not-vesting-contract"); - let vesting_contract = test.vesting_contract(); - - let owner = "alice"; - let (mixnode, sig, _) = test.mixnode_with_signature(owner, None); - let cost_params = fixtures::mix_node_cost_params_fixture(); - - let res = try_add_mixnode_on_behalf( - test.deps_mut(), - env, - mock_info(illegal_proxy.as_ref(), &good_mixnode_pledge()), - mixnode, - cost_params, - owner.to_string(), - sig, - ) - .unwrap_err(); - - assert_eq!( - res, - MixnetContractError::SenderIsNotVestingContract { - received: illegal_proxy, - vesting_contract, - } - ) - } - #[test] fn removing_mixnode_cant_be_performed_if_epoch_transition_is_in_progress() { let bad_states = vec![ @@ -761,23 +546,6 @@ pub mod tests { ); let mix_id = test.add_dummy_mixnode(owner, None); - let vesting_contract = test.vesting_contract(); - - // attempted to remove on behalf with invalid proxy (current is `None`) - let res = try_remove_mixnode_on_behalf( - test.deps_mut(), - env.clone(), - mock_info(vesting_contract.as_ref(), &[]), - owner.to_string(), - ); - - assert_eq!( - res, - Err(MixnetContractError::ProxyMismatch { - existing: "None".to_string(), - incoming: vesting_contract.into_string(), - }) - ); // "normal" unbonding succeeds and unbonding event is pushed to the pending epoch events let res = try_remove_mixnode(test.deps_mut(), env.clone(), info.clone()); @@ -799,35 +567,6 @@ pub mod tests { assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id })) } - #[test] - fn mixnode_remove_with_illegal_proxy() { - let mut test = TestSetup::new(); - let env = test.env(); - - let illegal_proxy = Addr::unchecked("not-vesting-contract"); - let vesting_contract = test.vesting_contract(); - - let owner = "alice"; - - test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone()); - - let res = try_remove_mixnode_on_behalf( - test.deps_mut(), - env, - mock_info(illegal_proxy.as_ref(), &[]), - owner.to_string(), - ) - .unwrap_err(); - - assert_eq!( - res, - MixnetContractError::SenderIsNotVestingContract { - received: illegal_proxy, - vesting_contract, - } - ) - } - #[test] fn mixnode_remove_is_not_allowed_if_there_are_pending_pledge_changes() { let mut test = TestSetup::new(); @@ -908,22 +647,7 @@ pub mod tests { ); let mix_id = test.add_dummy_mixnode(owner, None); - let vesting_contract = test.vesting_contract(); - // attempted to remove on behalf with invalid proxy (current is `None`) - let res = try_update_mixnode_config_on_behalf( - test.deps_mut(), - mock_info(vesting_contract.as_ref(), &[]), - update.clone(), - owner.to_string(), - ); - assert_eq!( - res, - Err(MixnetContractError::ProxyMismatch { - existing: "None".to_string(), - incoming: vesting_contract.into_string(), - }) - ); // "normal" update succeeds let res = try_update_mixnode_config(test.deps_mut(), info.clone(), update.clone()); assert!(res.is_ok()); @@ -943,41 +667,6 @@ pub mod tests { assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id })) } - #[test] - fn updating_mixnode_config_with_illegal_proxy() { - let mut test = TestSetup::new(); - - let illegal_proxy = Addr::unchecked("not-vesting-contract"); - let vesting_contract = test.vesting_contract(); - - let owner = "alice"; - - test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone()); - let update = MixNodeConfigUpdate { - host: "1.1.1.1:1234".to_string(), - mix_port: 1234, - verloc_port: 1235, - http_api_port: 1236, - version: "v1.2.3".to_string(), - }; - - let res = try_update_mixnode_config_on_behalf( - test.deps_mut(), - mock_info(illegal_proxy.as_ref(), &[]), - update, - owner.to_string(), - ) - .unwrap_err(); - - assert_eq!( - res, - MixnetContractError::SenderIsNotVestingContract { - received: illegal_proxy, - vesting_contract, - } - ) - } - #[test] fn mixnode_cost_params_cant_be_updated_when_epoch_transition_is_in_progress() { let bad_states = vec![ @@ -1042,23 +731,7 @@ pub mod tests { ); let mix_id = test.add_dummy_mixnode(owner, None); - let vesting_contract = test.vesting_contract(); - // attempted to remove on behalf with invalid proxy (current is `None`) - let res = try_update_mixnode_cost_params_on_behalf( - test.deps_mut(), - env.clone(), - mock_info(vesting_contract.as_ref(), &[]), - update.clone(), - owner.to_string(), - ); - assert_eq!( - res, - Err(MixnetContractError::ProxyMismatch { - existing: "None".to_string(), - incoming: vesting_contract.into_string(), - }) - ); // "normal" update succeeds let res = try_update_mixnode_cost_params( test.deps_mut(), @@ -1099,40 +772,6 @@ pub mod tests { assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id })) } - #[test] - fn updating_mixnode_cost_params_with_illegal_proxy() { - let mut test = TestSetup::new(); - let env = test.env(); - - let illegal_proxy = Addr::unchecked("not-vesting-contract"); - let vesting_contract = test.vesting_contract(); - - let owner = "alice"; - - test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone()); - let update = MixNodeCostParams { - profit_margin_percent: Percent::from_percentage_value(42).unwrap(), - interval_operating_cost: Coin::new(12345678, TEST_COIN_DENOM), - }; - - let res = try_update_mixnode_cost_params_on_behalf( - test.deps_mut(), - env, - mock_info(illegal_proxy.as_ref(), &[]), - update, - owner.to_string(), - ) - .unwrap_err(); - - assert_eq!( - res, - MixnetContractError::SenderIsNotVestingContract { - received: illegal_proxy, - vesting_contract, - } - ) - } - #[test] fn adding_mixnode_with_duplicate_sphinx_key_errors_out() { let mut test = TestSetup::new(); @@ -1240,69 +879,6 @@ pub mod tests { ) } - #[test] - fn is_not_allowed_if_theres_proxy_mismatch() { - let mut test = TestSetup::new(); - let env = test.env(); - - let owner_without_proxy = Addr::unchecked("no-proxy"); - let owner_with_proxy = Addr::unchecked("with-proxy"); - let proxy = Addr::unchecked("proxy"); - let wrong_proxy = Addr::unchecked("unrelated-proxy"); - - test.add_dummy_mixnode(owner_without_proxy.as_str(), None); - test.add_dummy_mixnode_with_illegal_proxy( - owner_with_proxy.as_str(), - None, - proxy.clone(), - ); - - let res = _try_increase_pledge( - test.deps_mut(), - env.clone(), - Vec::new(), - owner_without_proxy.clone(), - Some(proxy), - ); - assert_eq!( - res, - Err(MixnetContractError::ProxyMismatch { - existing: "None".to_string(), - incoming: "proxy".to_string(), - }) - ); - - let res = _try_increase_pledge( - test.deps_mut(), - env.clone(), - Vec::new(), - owner_with_proxy.clone(), - None, - ); - assert_eq!( - res, - Err(MixnetContractError::ProxyMismatch { - existing: "proxy".to_string(), - incoming: "None".to_string(), - }) - ); - - let res = _try_increase_pledge( - test.deps_mut(), - env, - Vec::new(), - owner_with_proxy.clone(), - Some(wrong_proxy), - ); - assert_eq!( - res, - Err(MixnetContractError::ProxyMismatch { - existing: "proxy".to_string(), - incoming: "unrelated-proxy".to_string(), - }) - ) - } - #[test] fn is_not_allowed_if_mixnode_has_unbonded_or_is_unbonding() { let mut test = TestSetup::new(); @@ -1457,35 +1033,6 @@ pub mod tests { } ); } - - #[test] - fn fails_for_illegal_proxy() { - let mut test = TestSetup::new(); - let env = test.env(); - - let illegal_proxy = Addr::unchecked("not-vesting-contract"); - let vesting_contract = test.vesting_contract(); - - let owner = "alice"; - - test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone()); - - let res = try_increase_pledge_on_behalf( - test.deps_mut(), - env, - mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]), - owner.to_string(), - ) - .unwrap_err(); - - assert_eq!( - res, - MixnetContractError::SenderIsNotVestingContract { - received: illegal_proxy, - vesting_contract, - } - ) - } } #[cfg(test)] @@ -1546,73 +1093,6 @@ pub mod tests { ) } - #[test] - fn is_not_allowed_if_theres_proxy_mismatch() { - let mut test = TestSetup::new(); - let env = test.env(); - - let owner_without_proxy = Addr::unchecked("no-proxy"); - let owner_with_proxy = Addr::unchecked("with-proxy"); - let proxy = Addr::unchecked("proxy"); - let wrong_proxy = Addr::unchecked("unrelated-proxy"); - - // just to make sure that after decrease the value would still be above the minimum - let stake = Uint128::new(100_000_000_000); - let decrease = test.coin(1000); - - test.add_dummy_mixnode(owner_without_proxy.as_str(), Some(stake)); - test.add_dummy_mixnode_with_illegal_proxy( - owner_with_proxy.as_str(), - Some(stake), - proxy.clone(), - ); - - let res = _try_decrease_pledge( - test.deps_mut(), - env.clone(), - decrease.clone(), - owner_without_proxy.clone(), - Some(proxy), - ); - assert_eq!( - res, - Err(MixnetContractError::ProxyMismatch { - existing: "None".to_string(), - incoming: "proxy".to_string(), - }) - ); - - let res = _try_decrease_pledge( - test.deps_mut(), - env.clone(), - decrease.clone(), - owner_with_proxy.clone(), - None, - ); - assert_eq!( - res, - Err(MixnetContractError::ProxyMismatch { - existing: "proxy".to_string(), - incoming: "None".to_string(), - }) - ); - - let res = _try_decrease_pledge( - test.deps_mut(), - env, - decrease, - owner_with_proxy.clone(), - Some(wrong_proxy), - ); - assert_eq!( - res, - Err(MixnetContractError::ProxyMismatch { - existing: "proxy".to_string(), - incoming: "unrelated-proxy".to_string(), - }) - ) - } - #[test] fn is_not_allowed_if_mixnode_has_unbonded_or_is_unbonding() { let mut test = TestSetup::new(); @@ -1809,38 +1289,5 @@ pub mod tests { } ); } - - #[test] - fn fails_for_illegal_proxy() { - let mut test = TestSetup::new(); - let env = test.env(); - - let stake = Uint128::new(100_000_000_000); - let decrease = test.coin(1000); - - let illegal_proxy = Addr::unchecked("not-vesting-contract"); - let vesting_contract = test.vesting_contract(); - - let owner = "alice"; - - test.add_dummy_mixnode_with_illegal_proxy(owner, Some(stake), illegal_proxy.clone()); - - let res = try_decrease_pledge_on_behalf( - test.deps_mut(), - env, - mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]), - decrease, - owner.to_string(), - ) - .unwrap_err(); - - assert_eq!( - res, - MixnetContractError::SenderIsNotVestingContract { - received: illegal_proxy, - vesting_contract, - } - ) - } } } diff --git a/contracts/mixnet/src/queued_migrations.rs b/contracts/mixnet/src/queued_migrations.rs index 8df3ef5d863..ab410897e92 100644 --- a/contracts/mixnet/src/queued_migrations.rs +++ b/contracts/mixnet/src/queued_migrations.rs @@ -1,2 +1,51 @@ // Copyright 2022-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 + +use crate::interval::storage as interval_storage; +use cosmwasm_std::{DepsMut, Order, Storage}; +use mixnet_contract_common::error::MixnetContractError; +use mixnet_contract_common::PendingEpochEventKind; + +fn ensure_no_pending_proxy_events(storage: &dyn Storage) -> Result<(), MixnetContractError> { + let last_executed = interval_storage::LAST_PROCESSED_EPOCH_EVENT.load(storage)?; + let last_inserted = interval_storage::EPOCH_EVENT_ID_COUNTER.load(storage)?; + + // no pending events + if last_executed == last_inserted { + return Ok(()); + } + + for maybe_event in + interval_storage::PENDING_EPOCH_EVENTS.range(storage, None, None, Order::Ascending) + { + let (id, event_data) = maybe_event?; + match event_data.kind { + PendingEpochEventKind::Delegate { proxy, .. } => { + if proxy.is_some() { + return Err(MixnetContractError::FailedMigration { + comment: format!( + "there is a pending vesting contract delegation with id {id}" + ), + }); + } + } + PendingEpochEventKind::Undelegate { proxy, .. } => { + if proxy.is_some() { + return Err(MixnetContractError::FailedMigration { + comment: format!( + "there is a pending vesting contract undelegation with id {id}" + ), + }); + } + } + _ => continue, + } + } + Ok(()) +} + +pub(crate) fn vesting_purge(deps: DepsMut) -> Result<(), MixnetContractError> { + ensure_no_pending_proxy_events(deps.storage)?; + + Ok(()) +} diff --git a/contracts/mixnet/src/rewards/transactions.rs b/contracts/mixnet/src/rewards/transactions.rs index 9aa417ae7ca..bbb0b345bc8 100644 --- a/contracts/mixnet/src/rewards/transactions.rs +++ b/contracts/mixnet/src/rewards/transactions.rs @@ -1,8 +1,19 @@ -// Copyright 2021-2023 - Nym Technologies SA +// Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use cosmwasm_std::{wasm_execute, Addr, DepsMut, Env, MessageInfo, Response}; - +use super::storage; +use crate::delegations::storage as delegations_storage; +use crate::interval::storage as interval_storage; +use crate::interval::storage::{push_new_epoch_event, push_new_interval_event}; +use crate::mixnodes::helpers::get_mixnode_details_by_owner; +use crate::mixnodes::storage as mixnodes_storage; +use crate::rewards::helpers; +use crate::rewards::helpers::update_and_save_last_rewarded; +use crate::support::helpers::{ + ensure_bonded, ensure_can_advance_epoch, ensure_epoch_in_progress_state, ensure_is_owner, + AttachSendTokens, +}; +use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::events::{ new_active_set_update_event, new_mix_rewarding_event, @@ -16,22 +27,6 @@ use mixnet_contract_common::reward_params::{ IntervalRewardingParamsUpdate, NodeRewardParams, Performance, }; use mixnet_contract_common::{Delegation, EpochState, MixId}; -use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg; - -use crate::delegations::storage as delegations_storage; -use crate::interval::storage as interval_storage; -use crate::interval::storage::{push_new_epoch_event, push_new_interval_event}; -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::rewards::helpers; -use crate::rewards::helpers::update_and_save_last_rewarded; -use crate::support::helpers::{ - ensure_bonded, ensure_can_advance_epoch, ensure_epoch_in_progress_state, ensure_is_owner, - ensure_proxy_match, ensure_sent_by_vesting_contract, send_to_proxy_or_owner, -}; - -use super::storage; pub(crate) fn try_reward_mixnode( deps: DepsMut<'_>, @@ -140,37 +135,16 @@ pub(crate) fn try_withdraw_operator_reward( deps: DepsMut<'_>, info: MessageInfo, ) -> Result { - _try_withdraw_operator_reward(deps, info.sender, None) -} - -pub(crate) fn try_withdraw_operator_reward_on_behalf( - deps: DepsMut<'_>, - info: MessageInfo, - owner: String, -) -> Result { - ensure_sent_by_vesting_contract(&info, deps.storage)?; - - let proxy = info.sender; - let owner = deps.api.addr_validate(&owner)?; - _try_withdraw_operator_reward(deps, owner, Some(proxy)) -} - -pub(crate) fn _try_withdraw_operator_reward( - deps: DepsMut<'_>, - owner: Addr, - proxy: Option, -) -> Result { - // we need to grab all of the node's details so we'd known original pledge alongside + // we need to grab all of the node's details, so we'd known original pledge alongside // all the earned rewards (and obviously to know if this node even exists and is still // in the bonded state) - let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())?.ok_or( + let mix_details = get_mixnode_details_by_owner(deps.storage, info.sender.clone())?.ok_or( MixnetContractError::NoAssociatedMixNodeBond { - owner: owner.clone(), + owner: info.sender.clone(), }, )?; let mix_id = mix_details.mix_id(); - ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?; ensure_bonded(&mix_details.bond_information)?; let reward = helpers::withdraw_operator_reward(deps.storage, mix_details)?; @@ -178,26 +152,13 @@ pub(crate) fn _try_withdraw_operator_reward( // if the reward is zero, don't track or send anything - there's no point if !reward.amount.is_zero() { - let return_tokens = send_to_proxy_or_owner(&proxy, &owner, vec![reward.clone()]); - response = response.add_message(return_tokens); - - if let Some(proxy) = &proxy { - // we can only attempt to send the message to the vesting contract if the proxy IS the vesting contract - // otherwise, we don't care - let vesting_contract = mixnet_params_storage::vesting_contract_address(deps.storage)?; - if proxy == vesting_contract { - let msg = VestingContractExecuteMsg::TrackReward { - amount: reward.clone(), - address: owner.clone().into_string(), - }; - let track_reward_message = wasm_execute(proxy, &msg, vec![])?; - response = response.add_message(track_reward_message); - } - } + response = response.send_tokens(&info.sender, reward.clone()) } Ok(response.add_event(new_withdraw_operator_reward_event( - &owner, &proxy, reward, mix_id, + &info.sender, + reward, + mix_id, ))) } @@ -205,37 +166,15 @@ pub(crate) fn try_withdraw_delegator_reward( deps: DepsMut<'_>, info: MessageInfo, mix_id: MixId, -) -> Result { - _try_withdraw_delegator_reward(deps, mix_id, info.sender, None) -} - -pub(crate) fn try_withdraw_delegator_reward_on_behalf( - deps: DepsMut<'_>, - info: MessageInfo, - mix_id: MixId, - owner: String, -) -> Result { - ensure_sent_by_vesting_contract(&info, deps.storage)?; - - let proxy = info.sender; - let owner = deps.api.addr_validate(&owner)?; - _try_withdraw_delegator_reward(deps, mix_id, owner, Some(proxy)) -} - -pub(crate) fn _try_withdraw_delegator_reward( - deps: DepsMut<'_>, - mix_id: MixId, - owner: Addr, - proxy: Option, ) -> Result { // see if the delegation even exists - let storage_key = Delegation::generate_storage_key(mix_id, &owner, proxy.as_ref()); + let storage_key = Delegation::generate_storage_key(mix_id, &info.sender, None); let delegation = match delegations_storage::delegations().may_load(deps.storage, storage_key)? { None => { return Err(MixnetContractError::NoMixnodeDelegationFound { mix_id, - address: owner.into_string(), - proxy: proxy.map(Addr::into_string), + address: info.sender.into_string(), + proxy: None, }); } Some(delegation) => delegation, @@ -257,33 +196,18 @@ pub(crate) fn _try_withdraw_delegator_reward( _ => (), }; - ensure_proxy_match(&proxy, &delegation.proxy)?; - let reward = helpers::withdraw_delegator_reward(deps.storage, delegation, mix_rewarding)?; let mut response = Response::new(); // if the reward is zero, don't track or send anything - there's no point if !reward.amount.is_zero() { - let return_tokens = send_to_proxy_or_owner(&proxy, &owner, vec![reward.clone()]); - response = response.add_message(return_tokens); - - if let Some(proxy) = &proxy { - // we can only attempt to send the message to the vesting contract if the proxy IS the vesting contract - // otherwise, we don't care - let vesting_contract = mixnet_params_storage::vesting_contract_address(deps.storage)?; - if proxy == vesting_contract { - let msg = VestingContractExecuteMsg::TrackReward { - amount: reward.clone(), - address: owner.clone().into_string(), - }; - let track_reward_message = wasm_execute(proxy, &msg, vec![])?; - response = response.add_message(track_reward_message); - } - } + response = response.send_tokens(&info.sender, reward.clone()) } Ok(response.add_event(new_withdraw_delegator_reward_event( - &owner, &proxy, reward, mix_id, + &info.sender, + reward, + mix_id, ))) } @@ -1405,13 +1329,10 @@ pub mod tests { #[cfg(test)] mod withdrawing_delegator_reward { - use cosmwasm_std::{coin, BankMsg, CosmosMsg, Decimal, Uint128}; - - use mixnet_contract_common::rewarding::helpers::truncate_reward_amount; - use crate::interval::pending_events; - use crate::support::tests::fixtures::TEST_COIN_DENOM; use crate::support::tests::test_helpers::{assert_eq_with_leeway, TestSetup}; + use cosmwasm_std::{BankMsg, CosmosMsg, Decimal, Uint128}; + use mixnet_contract_common::rewarding::helpers::truncate_reward_amount; use super::*; @@ -1742,60 +1663,14 @@ pub mod tests { let accumulated_actual = truncate_reward_amount(accumulated_quad); assert_eq_with_leeway(total_claimed, accumulated_actual, Uint128::new(6)); } - - #[test] - fn fails_for_illegal_proxy() { - let test = TestSetup::new(); - - let illegal_proxy = Addr::unchecked("not-vesting-contract"); - let vesting_contract = test.vesting_contract(); - - let mut test = TestSetup::new(); - let mix_id = - test.add_dummy_mixnode("mix-owner1", Some(Uint128::new(1_000_000_000_000))); - - let delegator = "delegator"; - - test.add_immediate_delegation_with_illegal_proxy( - delegator, - 100_000_000u128, - mix_id, - illegal_proxy.clone(), - ); - - // reward the node - test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id]); - test.start_epoch_transition(); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); - - let res = try_withdraw_delegator_reward_on_behalf( - test.deps_mut(), - mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]), - mix_id, - delegator.to_string(), - ) - .unwrap_err(); - - assert_eq!( - res, - MixnetContractError::SenderIsNotVestingContract { - received: illegal_proxy, - vesting_contract, - } - ) - } } #[cfg(test)] mod withdrawing_operator_reward { - use cosmwasm_std::{coin, BankMsg, CosmosMsg, Uint128}; - + use super::*; use crate::interval::pending_events; - use crate::support::tests::fixtures::TEST_COIN_DENOM; use crate::support::tests::test_helpers::TestSetup; - - use super::*; + use cosmwasm_std::{Addr, BankMsg, CosmosMsg, Uint128}; #[test] fn can_only_be_done_if_bond_exists() { @@ -1908,42 +1783,6 @@ pub mod tests { }) ); } - - #[test] - fn fails_for_illegal_proxy() { - let mut test = TestSetup::new(); - - let illegal_proxy = Addr::unchecked("not-vesting-contract"); - let vesting_contract = test.vesting_contract(); - - let owner = "mix-owner1"; - let mix_id = test.add_dummy_mixnode_with_illegal_proxy( - owner, - Some(Uint128::new(1_000_000_000_000)), - illegal_proxy.clone(), - ); - - // reward the node - test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id]); - test.start_epoch_transition(); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); - - let res = try_withdraw_operator_reward_on_behalf( - test.deps_mut(), - mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]), - owner.to_string(), - ) - .unwrap_err(); - - assert_eq!( - res, - MixnetContractError::SenderIsNotVestingContract { - received: illegal_proxy, - vesting_contract, - } - ) - } } #[cfg(test)] diff --git a/contracts/mixnet/src/support/helpers.rs b/contracts/mixnet/src/support/helpers.rs index 12c02a675a0..1b03a238be5 100644 --- a/contracts/mixnet/src/support/helpers.rs +++ b/contracts/mixnet/src/support/helpers.rs @@ -2,13 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 use crate::gateways::storage as gateways_storage; -use crate::mixnet_contract_settings::storage as mixnet_params_storage; use crate::mixnodes::storage as mixnodes_storage; -use cosmwasm_std::{wasm_execute, Addr, BankMsg, Coin, CosmosMsg, MessageInfo, Response, Storage}; +use cosmwasm_std::{Addr, BankMsg, Coin, CosmosMsg, Response, Storage}; use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::mixnode::PendingMixNodeChanges; -use mixnet_contract_common::{EpochState, EpochStatus, IdentityKeyRef, MixId, MixNodeBond}; -use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg; +use mixnet_contract_common::{EpochState, EpochStatus, IdentityKeyRef, MixNodeBond}; // helper trait to attach `Msg` to a response if it's provided #[allow(dead_code)] @@ -26,131 +24,16 @@ impl AttachOptionalMessage for Response { } } -// another helper trait to remove some duplicate code and consolidate comments regarding -// possible epoch progression halting behaviour -pub(crate) trait VestingTracking -where - Self: Sized, -{ - fn maybe_add_track_vesting_undelegation_message( - self, - storage: &dyn Storage, - proxy: Option, - owner: String, - mix_id: MixId, - amount: Coin, - ) -> Result; - - fn maybe_add_track_vesting_unbond_mixnode_message( - self, - storage: &dyn Storage, - proxy: Option, - owner: String, - amount: Coin, - ) -> Result; - - fn maybe_add_track_vesting_decrease_mixnode_pledge( - self, - storage: &dyn Storage, - proxy: Option, - owner: String, - amount: Coin, - ) -> Result; +pub(crate) trait AttachSendTokens { + fn send_tokens(self, to: impl AsRef, amount: Coin) -> Self; } -impl VestingTracking for Response { - fn maybe_add_track_vesting_undelegation_message( - self, - storage: &dyn Storage, - proxy: Option, - owner: String, - mix_id: MixId, - amount: Coin, - ) -> Result { - // if there's a proxy set (i.e. the vesting contract), send the track message - if let Some(proxy) = proxy { - let vesting_contract = mixnet_params_storage::vesting_contract_address(storage)?; - - // Note: this can INTENTIONALLY cause epoch progression halt if the proxy is not the vesting contract - // But this is fine, since this situation should have NEVER occurred in the first place - // (as all 'on_behalf' methods, including 'DelegateToMixnodeOnBehalf' that got us here, - // explicitly require the proxy to be the vesting contract) - // 'fixing' it would require manually inspecting the problematic event, investigating - // it's cause and manually (presumably via migration) clearing it. - if proxy != vesting_contract { - return Err(MixnetContractError::ProxyIsNotVestingContract { - received: proxy, - vesting_contract, - }); - } - - let msg = VestingContractExecuteMsg::TrackUndelegation { - owner, - mix_id, - amount, - }; - - let track_undelegate_message = wasm_execute(proxy, &msg, vec![])?; - Ok(self.add_message(track_undelegate_message)) - } else { - // there's no proxy so nothing to do - Ok(self) - } - } - - fn maybe_add_track_vesting_unbond_mixnode_message( - self, - storage: &dyn Storage, - proxy: Option, - owner: String, - amount: Coin, - ) -> Result { - // if there's a proxy set (i.e. the vesting contract), send the track message - if let Some(proxy) = proxy { - let vesting_contract = mixnet_params_storage::vesting_contract_address(storage)?; - - // exactly the same possible halting behaviour as in `maybe_add_track_vesting_undelegation_message`. - if proxy != vesting_contract { - return Err(MixnetContractError::ProxyIsNotVestingContract { - received: proxy, - vesting_contract, - }); - } - - let msg = VestingContractExecuteMsg::TrackUnbondMixnode { owner, amount }; - let track_unbond_message = wasm_execute(proxy, &msg, vec![])?; - Ok(self.add_message(track_unbond_message)) - } else { - // there's no proxy so nothing to do - Ok(self) - } - } - - fn maybe_add_track_vesting_decrease_mixnode_pledge( - self, - storage: &dyn Storage, - proxy: Option, - owner: String, - amount: Coin, - ) -> Result { - if let Some(proxy) = proxy { - let vesting_contract = mixnet_params_storage::vesting_contract_address(storage)?; - - // exactly the same possible halting behaviour as in `maybe_add_track_vesting_undelegation_message`. - if proxy != vesting_contract { - return Err(MixnetContractError::ProxyIsNotVestingContract { - received: proxy, - vesting_contract, - }); - } - - let msg = VestingContractExecuteMsg::TrackDecreasePledge { owner, amount }; - let track_decrease_pledge_message = wasm_execute(proxy, &msg, vec![])?; - Ok(self.add_message(track_decrease_pledge_message)) - } else { - // there's no proxy so nothing to do - Ok(self) - } +impl AttachSendTokens for Response { + fn send_tokens(self, to: impl AsRef, amount: Coin) -> Self { + self.add_message(BankMsg::Send { + to_address: to.as_ref().to_string(), + amount: vec![amount], + }) } } @@ -158,20 +41,6 @@ impl VestingTracking for Response { // api.debug(&*format!("\n\n\n=========================================\n{}\n=========================================\n\n\n", msg.into())); // } -/// Attempts to construct a `BankMsg` to send specified tokens to the provided -/// proxy address. If that's unavailable, the `BankMsg` will use the "owner" as the -/// "to_address". -pub(crate) fn send_to_proxy_or_owner( - proxy: &Option, - owner: &Addr, - amount: Vec, -) -> BankMsg { - BankMsg::Send { - to_address: proxy.as_ref().unwrap_or(owner).to_string(), - amount, - } -} - pub(crate) fn validate_pledge( mut pledge: Vec, minimum_pledge: Coin, @@ -337,39 +206,6 @@ pub(crate) fn ensure_is_owner( Ok(()) } -pub(crate) fn ensure_proxy_match( - actual: &Option, - expected: &Option, -) -> Result<(), MixnetContractError> { - if actual != expected { - return Err(MixnetContractError::ProxyMismatch { - existing: expected - .as_ref() - .map_or_else(|| "None".to_string(), |a| a.as_str().to_string()), - incoming: actual - .as_ref() - .map_or_else(|| "None".to_string(), |a| a.as_str().to_string()), - }); - } - Ok(()) -} - -pub(crate) fn ensure_sent_by_vesting_contract( - info: &MessageInfo, - storage: &dyn Storage, -) -> Result<(), MixnetContractError> { - let vesting_contract_address = - crate::mixnet_contract_settings::storage::vesting_contract_address(storage)?; - if info.sender != vesting_contract_address { - Err(MixnetContractError::SenderIsNotVestingContract { - received: info.sender.clone(), - vesting_contract: vesting_contract_address, - }) - } else { - Ok(()) - } -} - pub(crate) fn ensure_bonded(bond: &MixNodeBond) -> Result<(), MixnetContractError> { if bond.is_unbonding { return Err(MixnetContractError::MixnodeIsUnbonding { diff --git a/contracts/mixnet/src/support/tests/messages.rs b/contracts/mixnet/src/support/tests/messages.rs index a1bf5ab37ec..61a0815d917 100644 --- a/contracts/mixnet/src/support/tests/messages.rs +++ b/contracts/mixnet/src/support/tests/messages.rs @@ -22,7 +22,7 @@ pub(crate) fn valid_bond_gateway_msg( ..tests::fixtures::gateway_fixture() }; - let msg = gateway_bonding_sign_payload(deps, sender, None, gateway.clone(), stake); + let msg = gateway_bonding_sign_payload(deps, sender, gateway.clone(), stake); let owner_signature = ed25519_sign_message(msg, keypair.private_key()); let identity_key = keypair.public_key().to_base58_string(); diff --git a/contracts/mixnet/src/support/tests/mod.rs b/contracts/mixnet/src/support/tests/mod.rs index 2bd98eb1753..e8f7867a9de 100644 --- a/contracts/mixnet/src/support/tests/mod.rs +++ b/contracts/mixnet/src/support/tests/mod.rs @@ -14,8 +14,7 @@ pub mod test_helpers { use crate::delegations::storage as delegations_storage; use crate::delegations::transactions::try_delegate_to_mixnode; use crate::families::transactions::{try_create_family, try_join_family}; - use crate::gateways::storage as gateways_storage; - use crate::gateways::transactions::{try_add_gateway, try_add_gateway_on_behalf}; + use crate::gateways::transactions::try_add_gateway; use crate::interval::transactions::{ perform_pending_epoch_actions, perform_pending_interval_actions, try_begin_epoch_transition, }; @@ -27,9 +26,7 @@ pub mod test_helpers { }; use crate::mixnodes::storage as mixnodes_storage; use crate::mixnodes::storage::mixnode_bonds; - use crate::mixnodes::transactions::{ - try_add_mixnode, try_add_mixnode_on_behalf, try_remove_mixnode, - }; + use crate::mixnodes::transactions::{try_add_mixnode, try_remove_mixnode}; use crate::rewards::queries::{ query_pending_delegator_reward, query_pending_mixnode_operator_reward, }; @@ -45,7 +42,7 @@ pub mod test_helpers { use cosmwasm_std::testing::mock_info; use cosmwasm_std::testing::MockApi; use cosmwasm_std::testing::MockQuerier; - use cosmwasm_std::{coin, coins, Addr, Api, BankMsg, CosmosMsg, Storage}; + use cosmwasm_std::{coin, coins, Addr, BankMsg, CosmosMsg, Storage}; use cosmwasm_std::{Coin, Order}; use cosmwasm_std::{Decimal, Empty, MemoryStorage}; use cosmwasm_std::{Deps, OwnedDeps}; @@ -148,13 +145,6 @@ pub mod test_helpers { self.owner.clone() } - pub fn vesting_contract(&self) -> Addr { - mixnet_params_storage::CONTRACT_STATE - .load(self.deps().storage) - .unwrap() - .vesting_contract_address - } - pub fn coin(&self, amount: u128) -> Coin { coin(amount, rewarding_denom(self.deps().storage).unwrap()) } @@ -178,7 +168,6 @@ pub mod test_helpers { &mut self, family_owner_keys: &identity::KeyPair, member_node: IdentityKeyRef, - vesting: bool, ) -> MessageSignature { let identity = family_owner_keys.public_key().to_base58_string(); @@ -195,14 +184,7 @@ pub mod test_helpers { let nonce = signing_storage::get_signing_nonce(self.deps().storage, owner).unwrap(); - let proxy = if vesting { - Some(self.vesting_contract()) - } else { - None - }; - - let msg = - construct_family_join_permit(nonce, family_head, proxy, member_node.to_owned()); + let msg = construct_family_join_permit(nonce, family_head, member_node.to_owned()); let sig_bytes = family_owner_keys .private_key() @@ -217,13 +199,11 @@ pub mod test_helpers { member: &str, member_keys: &identity::KeyPair, head_keys: &identity::KeyPair, - vesting: bool, ) { let member_identity = member_keys.public_key().to_base58_string(); let head_identity = head_keys.public_key().to_base58_string(); - let join_permit = - self.generate_family_join_permit(head_keys, &member_identity, vesting); + let join_permit = self.generate_family_join_permit(head_keys, &member_identity); let family_head = FamilyHead::new(head_identity); try_join_family( @@ -235,12 +215,13 @@ pub mod test_helpers { .unwrap(); } + #[allow(dead_code)] pub fn create_dummy_mixnode_with_new_family( &mut self, head: &str, label: &str, ) -> (MixId, identity::KeyPair) { - let (mix_id, keys) = self.add_dummy_mixnode_with_proxy_and_keypair(head, None); + let (mix_id, keys) = self.add_dummy_mixnode_with_keypair(head, None); try_create_family(self.deps_mut(), mock_info(head, &[]), label.to_string()).unwrap(); (mix_id, keys) @@ -338,7 +319,7 @@ pub mod test_helpers { stake: Option, ) -> MessageSignature { let stake = self.make_mix_pledge(stake); - let msg = mixnode_bonding_sign_payload(self.deps(), owner, None, mixnode, stake); + let msg = mixnode_bonding_sign_payload(self.deps(), owner, mixnode, stake); ed25519_sign_message(msg, key) } @@ -359,13 +340,8 @@ pub mod test_helpers { ..tests::fixtures::mix_node_fixture() }; - let msg = mixnode_bonding_sign_payload( - self.deps(), - owner, - None, - mixnode.clone(), - stake.clone(), - ); + let msg = + mixnode_bonding_sign_payload(self.deps(), owner, mixnode.clone(), stake.clone()); let owner_signature = ed25519_sign_message(msg, keypair.private_key()); let info = mock_info(owner, &stake); @@ -389,156 +365,6 @@ pub mod test_helpers { (current_id_counter + 1, keypair) } - pub fn add_dummy_mixnode_with_proxy_and_keypair( - &mut self, - owner: &str, - stake: Option, - ) -> (MixId, identity::KeyPair) { - let stake = self.make_mix_pledge(stake); - - let proxy = self.vesting_contract(); - - let keypair = identity::KeyPair::new(&mut self.rng); - let identity_key = keypair.public_key().to_base58_string(); - let legit_sphinx_keys = nym_crypto::asymmetric::encryption::KeyPair::new(&mut self.rng); - - let mixnode = MixNode { - identity_key, - sphinx_key: legit_sphinx_keys.public_key().to_base58_string(), - ..tests::fixtures::mix_node_fixture() - }; - - let msg = mixnode_bonding_sign_payload( - self.deps(), - owner, - Some(proxy.clone()), - mixnode.clone(), - stake.clone(), - ); - let owner_signature = ed25519_sign_message(msg, keypair.private_key()); - - let info = mock_info(proxy.as_str(), &stake); - let current_id_counter = mixnodes_storage::MIXNODE_ID_COUNTER - .may_load(self.deps().storage) - .unwrap() - .unwrap_or_default(); - - let env = self.env(); - try_add_mixnode_on_behalf( - self.deps_mut(), - env, - info, - mixnode, - tests::fixtures::mix_node_cost_params_fixture(), - owner.to_string(), - owner_signature, - ) - .unwrap(); - - // newly added mixnode gets assigned the current counter + 1 - (current_id_counter + 1, keypair) - } - - pub fn add_dummy_mixnode_with_legal_proxy( - &mut self, - owner: &str, - stake: Option, - ) -> MixId { - self.add_dummy_mixnode_with_proxy_and_keypair(owner, stake) - .0 - } - - pub fn set_illegal_mixnode_proxy(&mut self, mix_id: MixId, proxy: Addr) { - let mut bond_details = mixnodes_storage::mixnode_bonds() - .load(self.deps().storage, mix_id) - .unwrap(); - bond_details.proxy = Some(proxy); - mixnodes_storage::mixnode_bonds() - .save(self.deps_mut().storage, mix_id, &bond_details) - .unwrap(); - } - - pub fn add_dummy_gateway_with_illegal_proxy( - &mut self, - owner: &str, - stake: Option, - proxy: Addr, - ) -> IdentityKey { - let gateway_identity = self.add_dummy_gateway_with_legal_proxy(owner, stake); - self.set_illegal_gateway_proxy(&gateway_identity, proxy); - gateway_identity - } - - pub fn set_illegal_gateway_proxy(&mut self, gateway_id: &str, proxy: Addr) { - let mut gateway = gateways_storage::gateways() - .load(self.deps().storage, gateway_id) - .unwrap(); - gateway.proxy = Some(proxy); - gateways_storage::gateways() - .save(self.deps_mut().storage, gateway_id, &gateway) - .unwrap(); - } - - pub fn add_dummy_gateway_with_legal_proxy( - &mut self, - owner: &str, - stake: Option, - ) -> IdentityKey { - let stake = match stake { - Some(amount) => { - let denom = rewarding_denom(self.deps().storage).unwrap(); - Coin { denom, amount } - } - None => minimum_mixnode_pledge(self.deps.as_ref().storage).unwrap(), - }; - - let keypair = identity::KeyPair::new(&mut self.rng); - let identity_key = keypair.public_key().to_base58_string(); - let legit_sphinx_keys = nym_crypto::asymmetric::encryption::KeyPair::new(&mut self.rng); - - let proxy = self.vesting_contract(); - - let gateway = Gateway { - identity_key, - sphinx_key: legit_sphinx_keys.public_key().to_base58_string(), - ..tests::fixtures::gateway_fixture() - }; - - let msg = gateway_bonding_sign_payload( - self.deps(), - owner, - Some(proxy.clone()), - gateway.clone(), - vec![stake.clone()], - ); - let owner_signature = ed25519_sign_message(msg, keypair.private_key()); - - let env = self.env(); - let info = mock_info(proxy.as_ref(), &[stake]); - - try_add_gateway_on_behalf( - self.deps_mut(), - env, - info, - gateway, - owner.to_string(), - owner_signature, - ) - .unwrap(); - keypair.public_key().to_base58_string() - } - - pub fn add_dummy_mixnode_with_illegal_proxy( - &mut self, - owner: &str, - stake: Option, - proxy: Addr, - ) -> MixId { - let mix_id = self.add_dummy_mixnode_with_legal_proxy(owner, stake); - self.set_illegal_mixnode_proxy(mix_id, proxy); - mix_id - } - pub fn mixnode_with_signature( &mut self, sender: &str, @@ -555,8 +381,7 @@ pub mod test_helpers { sphinx_key: legit_sphinx_keys.public_key().to_base58_string(), ..tests::fixtures::mix_node_fixture() }; - let msg = - mixnode_bonding_sign_payload(self.deps(), sender, None, mixnode.clone(), stake); + let msg = mixnode_bonding_sign_payload(self.deps(), sender, mixnode.clone(), stake); let owner_signature = ed25519_sign_message(msg, keypair.private_key()); (mixnode, owner_signature, keypair) @@ -579,8 +404,7 @@ pub mod test_helpers { ..tests::fixtures::gateway_fixture() }; - let msg = - gateway_bonding_sign_payload(self.deps(), sender, None, gateway.clone(), stake); + let msg = gateway_bonding_sign_payload(self.deps(), sender, gateway.clone(), stake); let owner_signature = ed25519_sign_message(msg, keypair.private_key()); (gateway, owner_signature) @@ -625,87 +449,10 @@ pub mod test_helpers { Addr::unchecked(delegator), target, amount, - None, ) .unwrap(); } - pub fn add_immediate_delegation_with_legal_proxy( - &mut self, - delegator: &str, - amount: impl Into, - target: MixId, - ) { - let denom = rewarding_denom(self.deps().storage).unwrap(); - let amount = Coin { - denom, - amount: amount.into(), - }; - let env = self.env(); - let proxy = self.vesting_contract(); - pending_events::delegate( - self.deps_mut(), - &env, - env.block.height, - Addr::unchecked(delegator), - target, - amount, - Some(proxy), - ) - .unwrap(); - } - - // to set illegal proxy we have to bypass "normal" flow and put the value - // directly into the storage - pub fn add_immediate_delegation_with_illegal_proxy( - &mut self, - delegator: &str, - amount: impl Into, - target: MixId, - proxy: Addr, - ) { - let denom = rewarding_denom(self.deps().storage).unwrap(); - let amount = Coin { - denom, - amount: amount.into(), - }; - - let owner = self.deps.api.addr_validate(delegator).unwrap(); - let storage_key = Delegation::generate_storage_key(target, &owner, Some(&proxy)); - - let mut mix_rewarding = self.mix_rewarding(target); - - let mut stored_delegation_amount = amount; - - if let Some(existing_delegation) = delegations_storage::delegations() - .may_load(&self.deps.storage, storage_key.clone()) - .unwrap() - { - let og_with_reward = mix_rewarding.undelegate(&existing_delegation).unwrap(); - stored_delegation_amount.amount += og_with_reward.amount; - } - - mix_rewarding - .add_base_delegation(stored_delegation_amount.amount) - .unwrap(); - - let delegation = Delegation::new( - owner, - target, - mix_rewarding.total_unit_reward, - stored_delegation_amount, - self.env.block.height, - Some(proxy), - ); - - delegations_storage::delegations() - .save(&mut self.deps.storage, storage_key, &delegation) - .unwrap(); - rewards_storage::MIXNODE_REWARDING - .save(&mut self.deps.storage, target, &mix_rewarding) - .unwrap(); - } - #[allow(unused)] pub fn add_delegation( &mut self, @@ -724,14 +471,8 @@ pub mod test_helpers { pub fn remove_immediate_delegation(&mut self, delegator: &str, target: MixId) { let height = self.env.block.height; - pending_events::undelegate( - self.deps_mut(), - height, - Addr::unchecked(delegator), - target, - None, - ) - .unwrap(); + pending_events::undelegate(self.deps_mut(), height, Addr::unchecked(delegator), target) + .unwrap(); } pub fn start_epoch_transition(&mut self) { @@ -1109,7 +850,6 @@ pub mod test_helpers { Addr::unchecked(format!("owner{}", i)), mix_id, tests::fixtures::good_mixnode_pledge().pop().unwrap(), - None, ) .unwrap(); } @@ -1205,7 +945,6 @@ pub mod test_helpers { pub fn mixnode_bonding_sign_payload( deps: Deps<'_>, owner: &str, - proxy: Option, mixnode: MixNode, stake: Vec, ) -> SignableMixNodeBondingMsg { @@ -1214,14 +953,13 @@ pub mod test_helpers { signing_storage::get_signing_nonce(deps.storage, Addr::unchecked(owner)).unwrap(); let payload = MixnodeBondingPayload::new(mixnode, cost_params); - let content = ContractMessageContent::new(Addr::unchecked(owner), proxy, stake, payload); + let content = ContractMessageContent::new(Addr::unchecked(owner), stake, payload); SignableMixNodeBondingMsg::new(nonce, content) } pub fn gateway_bonding_sign_payload( deps: Deps<'_>, owner: &str, - proxy: Option, gateway: Gateway, stake: Vec, ) -> SignableGatewayBondingMsg { @@ -1229,7 +967,7 @@ pub mod test_helpers { signing_storage::get_signing_nonce(deps.storage, Addr::unchecked(owner)).unwrap(); let payload = GatewayBondingPayload::new(gateway); - let content = ContractMessageContent::new(Addr::unchecked(owner), proxy, stake, payload); + let content = ContractMessageContent::new(Addr::unchecked(owner), stake, payload); SignableGatewayBondingMsg::new(nonce, content) } diff --git a/contracts/mixnet/src/vesting_migration.rs b/contracts/mixnet/src/vesting_migration.rs new file mode 100644 index 00000000000..3faf826ec05 --- /dev/null +++ b/contracts/mixnet/src/vesting_migration.rs @@ -0,0 +1,95 @@ +// Copyright 2024 - Nym Technologies SA +// 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 { + 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 { + 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![], + )?)) +} diff --git a/contracts/vesting/schema/nym-vesting-contract.json b/contracts/vesting/schema/nym-vesting-contract.json index 7d016b8e68f..1a7b2927844 100644 --- a/contracts/vesting/schema/nym-vesting-contract.json +++ b/contracts/vesting/schema/nym-vesting-contract.json @@ -691,6 +691,54 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "track_migrated_mixnode" + ], + "properties": { + "track_migrated_mixnode": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "track_migrated_delegation" + ], + "properties": { + "track_migrated_delegation": { + "type": "object", + "required": [ + "mix_id", + "owner" + ], + "properties": { + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { diff --git a/contracts/vesting/schema/raw/execute.json b/contracts/vesting/schema/raw/execute.json index be6d6a73ff0..9723294d67c 100644 --- a/contracts/vesting/schema/raw/execute.json +++ b/contracts/vesting/schema/raw/execute.json @@ -669,6 +669,54 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "track_migrated_mixnode" + ], + "properties": { + "track_migrated_mixnode": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "track_migrated_delegation" + ], + "properties": { + "track_migrated_delegation": { + "type": "object", + "required": [ + "mix_id", + "owner" + ], + "properties": { + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { diff --git a/contracts/vesting/src/contract.rs b/contracts/vesting/src/contract.rs index a9689e33907..a9ef9401a68 100644 --- a/contracts/vesting/src/contract.rs +++ b/contracts/vesting/src/contract.rs @@ -70,55 +70,12 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { 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,22 @@ 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(), + }), } } diff --git a/contracts/vesting/src/traits/bonding_account.rs b/contracts/vesting/src/traits/bonding_account.rs index e5e415e30c1..8e4c7c6a620 100644 --- a/contracts/vesting/src/traits/bonding_account.rs +++ b/contracts/vesting/src/traits/bonding_account.rs @@ -61,6 +61,10 @@ pub trait MixnodeBondingAccount { new_costs: MixNodeCostParams, storage: &mut dyn Storage, ) -> Result; + fn try_track_migrated_mixnode( + &self, + storage: &mut dyn Storage, + ) -> Result<(), VestingContractError>; } pub trait GatewayBondingAccount { diff --git a/contracts/vesting/src/traits/delegating_account.rs b/contracts/vesting/src/traits/delegating_account.rs index 629cfee0509..2e7b5a65d38 100644 --- a/contracts/vesting/src/traits/delegating_account.rs +++ b/contracts/vesting/src/traits/delegating_account.rs @@ -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>; } diff --git a/contracts/vesting/src/transactions.rs b/contracts/vesting/src/transactions.rs index adbfe394363..a51f084862e 100644 --- a/contracts/vesting/src/transactions.rs +++ b/contracts/vesting/src/transactions.rs @@ -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 { + 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 { + 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, diff --git a/contracts/vesting/src/vesting/account/delegating_account.rs b/contracts/vesting/src/vesting/account/delegating_account.rs index 0371bc7cc34..d01bea67891 100644 --- a/contracts/vesting/src/vesting/account/delegating_account.rs +++ b/contracts/vesting/src/vesting/account/delegating_account.rs @@ -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) + } } diff --git a/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs b/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs index 29ee780ff98..ee14207d3e0 100644 --- a/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs +++ b/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs @@ -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) + } } diff --git a/contracts/vesting/src/vesting/mod.rs b/contracts/vesting/src/vesting/mod.rs index b16efbdd23f..d8da4050084 100644 --- a/contracts/vesting/src/vesting/mod.rs +++ b/contracts/vesting/src/vesting/mod.rs @@ -13,14 +13,14 @@ pub fn populate_vesting_periods( #[cfg(test)] mod tests { use crate::contract::*; - use crate::storage::*; + use crate::support::tests::helpers::vesting_account_percent_fixture; use crate::support::tests::helpers::{ init_contract, vesting_account_mid_fixture, vesting_account_new_fixture, TEST_COIN_DENOM, }; use crate::traits::DelegatingAccount; + use crate::traits::GatewayBondingAccount; use crate::traits::VestingAccount; - use crate::traits::{GatewayBondingAccount, MixnodeBondingAccount}; use crate::vesting::account::StorableVestingAccountExt; use crate::vesting::populate_vesting_periods; use contracts_common::signing::MessageSignature; @@ -36,162 +36,56 @@ mod tests { fn test_account_creation() { let mut deps = init_contract(); let env = mock_env(); - let info = mock_info("not_admin", &coins(1_000_000_000_000, TEST_COIN_DENOM)); + let msg = ExecuteMsg::CreateAccount { owner_address: "owner".to_string(), staking_address: Some("staking".to_string()), vesting_spec: None, cap: Some(PledgeCap::Absolute(Uint128::from(100_000_000_000u128))), }; - // Try creating an account when not admin - let response = execute(deps.as_mut(), env.clone(), info, msg.clone()); - assert!(response.is_err()); let info = mock_info("admin", &coins(1_000_000_000_000, TEST_COIN_DENOM)); - let _response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()); - let created_account = load_account(Addr::unchecked("owner"), &deps.storage) - .unwrap() - .unwrap(); - - assert_eq!( - created_account.load_balance(&deps.storage).unwrap(), - // One was liquidated - Uint128::new(1_000_000_000_000) - ); - - // nothing is saved for "staking" account! - let created_account_test_by_staking = - load_account(Addr::unchecked("staking"), &deps.storage).unwrap(); - assert!(created_account_test_by_staking.is_none()); - - // but we can stake on its behalf! - let stake_msg = ExecuteMsg::DelegateToMixnode { - on_behalf_of: Some("owner".to_string()), - mix_id: 42, - amount: coin(500, TEST_COIN_DENOM), - }; - - let response = execute( - deps.as_mut(), - env.clone(), - mock_info("staking", &[]), - stake_msg, - ); - assert!(response.is_ok()); - + let response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()); assert_eq!( - created_account.load_balance(&deps.storage).unwrap(), - // One was liquidated - Uint128::new(999_999_999_500) + response, + Err(VestingContractError::Other { + message: "the contract has been disabled".to_string() + }) ); - - // Try create the same account again - let response = execute(deps.as_mut(), env.clone(), info, msg); - assert!(response.is_err()); - - let account_again = vesting_account_new_fixture(&mut deps.storage, &env); - assert_eq!(created_account.storage_key(), 1); - assert_ne!(created_account.storage_key(), account_again.storage_key()); } #[test] fn test_ownership_transfer() { let mut deps = init_contract(); - let mut env = mock_env(); + let env = mock_env(); let info = mock_info("owner", &[]); - let account = vesting_account_new_fixture(&mut deps.storage, &env); - let staker = account.staking_address().unwrap(); let msg = ExecuteMsg::TransferOwnership { to_address: "new_owner".to_string(), }; - let _response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()).unwrap(); - let new_owner_account = load_account(Addr::unchecked("new_owner"), &deps.storage) - .unwrap() - .unwrap(); + let response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()); assert_eq!( - new_owner_account.load_balance(&deps.storage), - account.load_balance(&deps.storage) - ); - - // Check old account is gone - let old_owner_account = load_account(Addr::unchecked("owner"), &deps.storage).unwrap(); - assert!(old_owner_account.is_none()); - - // Not the owner - let response = execute(deps.as_mut(), env.clone(), info, msg); - assert!(response.is_err()); - - // can't stake on behalf of the original owner anymore, but we can do it for the new one! - let stake_msg = ExecuteMsg::DelegateToMixnode { - on_behalf_of: Some("owner".to_string()), - mix_id: 42, - amount: coin(500, TEST_COIN_DENOM), - }; - let response = execute( - deps.as_mut(), - env.clone(), - mock_info(staker.as_ref(), &[]), - stake_msg, - ); - assert!(response.is_err()); - - let new_stake_msg = ExecuteMsg::DelegateToMixnode { - on_behalf_of: Some("new_owner".to_string()), - mix_id: 42, - amount: coin(500, TEST_COIN_DENOM), - }; - let response = execute( - deps.as_mut(), - env.clone(), - mock_info(staker.as_ref(), &[]), - new_stake_msg, + response, + Err(VestingContractError::Other { + message: "the contract has been disabled".to_string() + }) ); - assert!(response.is_ok()); - - let info = mock_info("new_owner", &[]); - let msg = ExecuteMsg::UpdateStakingAddress { - to_address: Some("new_staking".to_string()), - }; - let _response = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); - - let msg = ExecuteMsg::WithdrawVestedCoins { - amount: Coin { - amount: Uint128::new(1), - denom: TEST_COIN_DENOM.to_string(), - }, - }; - let info = mock_info("new_owner", &[]); - env.block.time = Timestamp::from_nanos(env.block.time.nanos() + 100_000_000_000_000_000); - let response = execute(deps.as_mut(), env.clone(), info, msg.clone()); - assert!(response.is_ok()); - - let info = mock_info("owner", &[]); - let response = execute(deps.as_mut(), env.clone(), info, msg); - assert!(response.is_err()); } #[test] fn test_staking_account() { let mut deps = init_contract(); - let mut env = mock_env(); + let env = mock_env(); let info = mock_info("staking", &[]); let msg = ExecuteMsg::TransferOwnership { to_address: "new_owner".to_string(), }; let response = execute(deps.as_mut(), env.clone(), info.clone(), msg); - // Only owner can transfer - assert!(response.is_err()); - - let msg = ExecuteMsg::WithdrawVestedCoins { - amount: Coin { - amount: Uint128::new(1), - denom: "nym".to_string(), - }, - }; - env.block.time = Timestamp::from_nanos(env.block.time.nanos() + 100_000_000_000_000_000); - let response = execute(deps.as_mut(), env, info, msg); - // Only owner can withdraw - assert!(response.is_err()); + assert_eq!( + response, + Err(VestingContractError::Other { + message: "the contract has been disabled".to_string() + }) + ); } #[test] @@ -213,31 +107,12 @@ mod tests { mock_info(original_staker.as_ref(), &[]), stake_msg.clone(), ); - assert!(response.is_ok()); - - let info = mock_info("owner", &[]); - let msg = ExecuteMsg::UpdateStakingAddress { - to_address: Some("new_staking".to_string()), - }; - let _response = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); - - // the old staking account can't do any staking anymore! - let response = execute( - deps.as_mut(), - env.clone(), - mock_info(original_staker.as_ref(), &[]), - stake_msg.clone(), - ); - assert!(response.is_err()); - - // but the new one can - let response = execute( - deps.as_mut(), - env.clone(), - mock_info("new_staking", &[]), - stake_msg, + assert_eq!( + response, + Err(VestingContractError::Other { + message: "the contract has been disabled".to_string() + }) ); - assert!(response.is_ok()); } #[test] @@ -245,66 +120,27 @@ mod tests { let mut deps = init_contract(); let env = mock_env(); - let amount1 = coin(1000000000, "unym"); - let amount2 = coin(100, "unym"); + let amount = coin(1000000000, "unym"); // create the accounts - let msg1 = ExecuteMsg::CreateAccount { + let msg = ExecuteMsg::CreateAccount { owner_address: "vesting1".to_string(), staking_address: None, vesting_spec: None, cap: None, }; - let res1 = execute( - deps.as_mut(), - env.clone(), - mock_info("admin", &[amount1.clone()]), - msg1, - ); - assert!(res1.is_ok()); - - let msg2 = ExecuteMsg::CreateAccount { - owner_address: "vesting2".to_string(), - staking_address: None, - vesting_spec: None, - cap: None, - }; - let res2 = execute( - deps.as_mut(), - env.clone(), - mock_info("admin", &[amount2.clone()]), - msg2, - ); - assert!(res2.is_ok()); - - let vesting1 = try_get_vesting_coins("vesting1", None, env.clone(), deps.as_ref()).unwrap(); - assert_eq!(vesting1, amount1); - - let vesting2 = try_get_vesting_coins("vesting2", None, env.clone(), deps.as_ref()).unwrap(); - assert_eq!(vesting2, amount2); - - let staking_address_change = ExecuteMsg::UpdateStakingAddress { - to_address: Some("vesting1".to_string()), - }; - let res = execute( + let response = execute( deps.as_mut(), env.clone(), - mock_info("vesting2", &[]), - staking_address_change, + mock_info("admin", &[amount.clone()]), + msg, ); assert_eq!( - Err(VestingContractError::StakingAccountExists( - "vesting1".to_string() - )), - res + response, + Err(VestingContractError::Other { + message: "the contract has been disabled".to_string() + }) ); - - // ensure nothing has changed! - let vesting1 = try_get_vesting_coins("vesting1", None, env.clone(), deps.as_ref()).unwrap(); - assert_eq!(vesting1, amount1); - - let vesting2 = try_get_vesting_coins("vesting2", None, env, deps.as_ref()).unwrap(); - assert_eq!(vesting2, amount2); } #[test] @@ -566,63 +402,13 @@ mod tests { }; let info = mock_info("admin", &coins(1_000_000_000_000, TEST_COIN_DENOM)); - let _response = execute(deps.as_mut(), env.clone(), info, msg); - let account = load_account(Addr::unchecked("owner"), &deps.storage) - .unwrap() - .unwrap(); - - // Try delegating too much - let err = account.try_delegate_to_mixnode( - 1, - Coin { - amount: Uint128::new(1_000_000_000_001), - denom: TEST_COIN_DENOM.to_string(), - }, - &env, - &mut deps.storage, - ); - assert!(err.is_err()); - - let ok = account.try_delegate_to_mixnode( - 1, - Coin { - amount: Uint128::new(90_000_000_000), - denom: TEST_COIN_DENOM.to_string(), - }, - &env, - &mut deps.storage, - ); - assert!(ok.is_ok()); - - // Fails due to delegation locked delegation cap - let ok = account.try_delegate_to_mixnode( - 1, - Coin { - amount: Uint128::new(20_000_000_000), - denom: TEST_COIN_DENOM.to_string(), - }, - &env, - &mut deps.storage, - ); - assert!(ok.is_err()); - - let balance = account.load_balance(&deps.storage).unwrap(); - assert_eq!(balance, Uint128::new(910000000000)); - - // Try delegating too much againcalca - let err = account.try_delegate_to_mixnode( - 1, - Coin { - amount: Uint128::new(500_000_000_001), - denom: TEST_COIN_DENOM.to_string(), - }, - &env, - &mut deps.storage, + let response = execute(deps.as_mut(), env.clone(), info, msg); + assert_eq!( + response, + Err(VestingContractError::Other { + message: "the contract has been disabled".to_string() + }) ); - assert!(err.is_err()); - - let total_delegations = account.total_delegations_for_mix(1, &deps.storage).unwrap(); - assert_eq!(Uint128::new(90_000_000_000), total_delegations); } #[test] @@ -649,52 +435,24 @@ mod tests { amount: Uint128::new(40), }, }; - // Try delegating too much - let err = account.try_bond_mixnode( - mix_node.clone(), - cost_params.clone(), - MessageSignature::from(vec![1, 2, 3]), - Coin { - amount: Uint128::new(1_000_000_000_001), - denom: TEST_COIN_DENOM.to_string(), - }, - &env, - &mut deps.storage, - ); - assert!(err.is_err()); - - let ok = account.try_bond_mixnode( - mix_node.clone(), - cost_params.clone(), - MessageSignature::from(vec![1, 2, 3]), - Coin { - amount: Uint128::new(90_000_000_000), - denom: TEST_COIN_DENOM.to_string(), - }, - &env, - &mut deps.storage, - ); - assert!(ok.is_ok()); - let balance = account.load_balance(&deps.storage).unwrap(); - assert_eq!(balance, Uint128::new(910_000_000_000)); - - // Try delegating too much again - let err = account.try_bond_mixnode( + let msg = ExecuteMsg::BondMixnode { mix_node, cost_params, - MessageSignature::from(vec![1, 2, 3]), - Coin { - amount: Uint128::new(10_000_000_001), + owner_signature: vec![1, 2, 3, 4].into(), + amount: Coin { + amount: Uint128::new(90_000_000_000), denom: TEST_COIN_DENOM.to_string(), }, - &env, - &mut deps.storage, + }; + let info = mock_info(account.owner_address.as_str(), &[]); + let response = execute(deps.as_mut(), env.clone(), info, msg); + assert_eq!( + response, + Err(VestingContractError::Other { + message: "the contract has been disabled".to_string() + }) ); - assert!(err.is_err()); - - let pledge = account.load_mixnode_pledge(&deps.storage).unwrap().unwrap(); - assert_eq!(Uint128::new(90_000_000_000), pledge.amount().amount); } #[test] diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 061b49cc26e..c037000b2cc 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -656,25 +656,6 @@ dependencies = [ "strsim", ] -[[package]] -name = "clap_complete" -version = "4.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc443334c81a804575546c5a8a79b4913b50e28d69232903604cada1de817ce" -dependencies = [ - "clap", -] - -[[package]] -name = "clap_complete_fig" -version = "4.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fee1d30a51305a6c2ed3fc5709be3c8af626c9c958e04dd9ae94e27bcbce9f" -dependencies = [ - "clap", - "clap_complete", -] - [[package]] name = "clap_derive" version = "4.4.7" @@ -3128,9 +3109,6 @@ dependencies = [ name = "nym-bin-common" version = "0.6.0" dependencies = [ - "clap", - "clap_complete", - "clap_complete_fig", "const-str", "log", "pretty_env_logger", @@ -3283,6 +3261,7 @@ version = "0.1.0" dependencies = [ "async-trait", "http 1.1.0", + "nym-bin-common", "reqwest 0.12.4", "serde", "serde_json", diff --git a/nym-wallet/src-tauri/src/error.rs b/nym-wallet/src-tauri/src/error.rs index 1ed617dc4bf..e968f269d43 100644 --- a/nym-wallet/src-tauri/src/error.rs +++ b/nym-wallet/src-tauri/src/error.rs @@ -134,6 +134,10 @@ pub enum BackendError { WalletValidatorConnectionFailed, #[error("No defined default validator URL")] WalletNoDefaultValidator, + #[error( + "this vesting operation has been disabled. please use the non-vesting variant instead." + )] + UnsupportedVestingOperation, #[error(transparent)] WalletError { diff --git a/nym-wallet/src-tauri/src/operations/helpers.rs b/nym-wallet/src-tauri/src/operations/helpers.rs index 9f66f292894..2b5631eb580 100644 --- a/nym-wallet/src-tauri/src/operations/helpers.rs +++ b/nym-wallet/src-tauri/src/operations/helpers.rs @@ -12,7 +12,7 @@ use nym_mixnet_contract_common::{ construct_mixnode_bonding_sign_payload, Gateway, GatewayBondingPayload, MixNode, MixNodeCostParams, SignableGatewayBondingMsg, SignableMixNodeBondingMsg, }; -use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, NymContractsProvider}; +use nym_validator_client::nyxd::contract_traits::MixnetQueryClient; use nym_validator_client::nyxd::error::NyxdError; use nym_validator_client::nyxd::Coin; use nym_validator_client::DirectSigningHttpRpcValidatorClient; @@ -21,7 +21,6 @@ use nym_validator_client::DirectSigningHttpRpcValidatorClient; #[async_trait] pub(crate) trait AddressAndNonceProvider { async fn get_signing_nonce(&self) -> Result; - fn vesting_contract_address(&self) -> Addr; fn cw_address(&self) -> Addr; } @@ -31,30 +30,11 @@ impl AddressAndNonceProvider for DirectSigningHttpRpcValidatorClient { self.nyxd.get_signing_nonce(&self.nyxd.address()).await } - fn vesting_contract_address(&self) -> Addr { - // the call to unchecked is fine here as we're converting directly from `AccountId` - // which must have been a valid bech32 address - Addr::unchecked( - self.nyxd - .vesting_contract_address() - .expect("unknown vesting contract address") - .as_ref(), - ) - } - fn cw_address(&self) -> Addr { self.nyxd.cw_address() } } -fn proxy(client: &P, vesting: bool) -> Option { - if vesting { - Some(client.vesting_contract_address()) - } else { - None - } -} - // since the message has to go back to the user due to the increasing nonce, we might as well sign the entire payload pub(crate) async fn create_mixnode_bonding_sign_payload( client: &P, @@ -63,14 +43,15 @@ pub(crate) async fn create_mixnode_bonding_sign_payload Result { + if vesting { + return Err(BackendError::UnsupportedVestingOperation); + } let sender = client.cw_address(); - let proxy = proxy(client, vesting); let nonce = client.get_signing_nonce().await?; Ok(construct_mixnode_bonding_sign_payload( nonce, sender, - proxy, pledge.into(), mix_node, cost_params, @@ -85,6 +66,9 @@ pub(crate) async fn verify_mixnode_bonding_sign_payload Result<(), BackendError> { + if vesting { + return Err(BackendError::UnsupportedVestingOperation); + } let identity_key = identity::PublicKey::from_base58_string(&mix_node.identity_key)?; let signature = identity::Signature::from_bytes(msg_signature.as_ref())?; @@ -118,10 +102,12 @@ pub(crate) async fn create_gateway_bonding_sign_payload Result { + if vesting { + return Err(BackendError::UnsupportedVestingOperation); + } let payload = GatewayBondingPayload::new(gateway); let sender = client.cw_address(); - let proxy = proxy(client, vesting); - let content = ContractMessageContent::new(sender, proxy, vec![pledge.into()], payload); + let content = ContractMessageContent::new(sender, vec![pledge.into()], payload); let nonce = client.get_signing_nonce().await?; Ok(SignableMessage::new(nonce, content)) @@ -134,6 +120,9 @@ pub(crate) async fn verify_gateway_bonding_sign_payload Result<(), BackendError> { + if vesting { + return Err(BackendError::UnsupportedVestingOperation); + } let identity_key = identity::PublicKey::from_base58_string(&gateway.identity_key)?; let signature = identity::Signature::from_bytes(msg_signature.as_ref())?; @@ -170,7 +159,6 @@ mod tests { struct MockClient { address: Addr, - vesting_contract: Addr, signing_nonce: Nonce, } @@ -180,10 +168,6 @@ mod tests { Ok(self.signing_nonce) } - fn vesting_contract_address(&self) -> Addr { - self.vesting_contract.clone() - } - fn cw_address(&self) -> Addr { self.address.clone() } @@ -211,7 +195,6 @@ mod tests { let dummy_account = Addr::unchecked("n16t2umcd83zjpl5puyuuq6lgmy4p3qedjd8ynn6"); let dummy_client = MockClient { address: dummy_account, - vesting_contract: Addr::unchecked("n17tj0a0w6v7r2dc54rnkzfza6s8hxs87rj273a5"), signing_nonce: 42, }; @@ -240,16 +223,8 @@ mod tests { dummy_pledge.clone(), true, ) - .await - .unwrap(); - - let plaintext_vesting = signing_msg_vesting.to_plaintext().unwrap(); - let sig_vesting: MessageSignature = identity_keypair - .private_key() - .sign(&plaintext_vesting) - .to_bytes() - .as_ref() - .into(); + .await; + assert!(signing_msg_vesting.is_err()); let res = verify_mixnode_bonding_sign_payload( &dummy_client, @@ -262,28 +237,6 @@ mod tests { .await; assert!(res.is_ok()); - let res = verify_mixnode_bonding_sign_payload( - &dummy_client, - &dummy_mixnode, - &dummy_cost_params, - &dummy_pledge, - true, - &sig_vesting, - ) - .await; - assert!(res.is_ok()); - - let res = verify_mixnode_bonding_sign_payload( - &dummy_client, - &dummy_mixnode, - &dummy_cost_params, - &dummy_pledge, - false, - &sig_vesting, - ) - .await; - assert!(res.is_err()); - let res = verify_mixnode_bonding_sign_payload( &dummy_client, &dummy_mixnode, @@ -315,7 +268,6 @@ mod tests { let dummy_account = Addr::unchecked("n16t2umcd83zjpl5puyuuq6lgmy4p3qedjd8ynn6"); let dummy_client = MockClient { address: dummy_account, - vesting_contract: Addr::unchecked("n17tj0a0w6v7r2dc54rnkzfza6s8hxs87rj273a5"), signing_nonce: 42, }; @@ -342,16 +294,8 @@ mod tests { dummy_pledge.clone(), true, ) - .await - .unwrap(); - - let plaintext_vesting = signing_msg_vesting.to_plaintext().unwrap(); - let sig_vesting: MessageSignature = identity_keypair - .private_key() - .sign(&plaintext_vesting) - .to_bytes() - .as_ref() - .into(); + .await; + assert!(signing_msg_vesting.is_err()); let res = verify_gateway_bonding_sign_payload( &dummy_client, @@ -363,26 +307,6 @@ mod tests { .await; assert!(res.is_ok()); - let res = verify_gateway_bonding_sign_payload( - &dummy_client, - &dummy_gateway, - &dummy_pledge, - true, - &sig_vesting, - ) - .await; - assert!(res.is_ok()); - - let res = verify_gateway_bonding_sign_payload( - &dummy_client, - &dummy_gateway, - &dummy_pledge, - false, - &sig_vesting, - ) - .await; - assert!(res.is_err()); - let res = verify_gateway_bonding_sign_payload( &dummy_client, &dummy_gateway, diff --git a/tools/nym-cli/src/validator/mixnet/delegators/mod.rs b/tools/nym-cli/src/validator/mixnet/delegators/mod.rs index 8ccfbd59a3f..08e5df592ab 100644 --- a/tools/nym-cli/src/validator/mixnet/delegators/mod.rs +++ b/tools/nym-cli/src/validator/mixnet/delegators/mod.rs @@ -34,7 +34,10 @@ pub(crate) async fn execute( } nym_cli_commands::validator::mixnet::delegators::MixnetDelegatorsCommands::List(args) => { nym_cli_commands::validator::mixnet::delegators::query_for_delegations::execute(args, create_signing_client_with_nym_api(global_args, network_details)?).await - } + }, + nym_cli_commands::validator::mixnet::delegators::MixnetDelegatorsCommands::MigrateVestedDelegation(args) => { + nym_cli_commands::validator::mixnet::delegators::migrate_vested_delegation::migrate_vested_delegation(args, create_signing_client(global_args, network_details)?).await + }, } Ok(()) } diff --git a/tools/nym-cli/src/validator/mixnet/operators/mixnodes/mod.rs b/tools/nym-cli/src/validator/mixnet/operators/mixnodes/mod.rs index b3cf6d4e28d..ac2d301f4fd 100644 --- a/tools/nym-cli/src/validator/mixnet/operators/mixnodes/mod.rs +++ b/tools/nym-cli/src/validator/mixnet/operators/mixnodes/mod.rs @@ -48,7 +48,10 @@ pub(crate) async fn execute( nym_cli_commands::validator::mixnet::operators::mixnode::MixnetOperatorsMixnodeCommands::DecreasePledgeVesting(args) => { nym_cli_commands::validator::mixnet::operators::mixnode::vesting_decrease_pledge::vesting_decrease_pledge(args, create_signing_client(global_args, network_details)?).await } - _ => unreachable!(), + nym_cli_commands::validator::mixnet::operators::mixnode::MixnetOperatorsMixnodeCommands::MigrateVestedNode(args) => { + nym_cli_commands::validator::mixnet::operators::mixnode::migrate_vested_mixnode::migrate_vested_mixnode(args, create_signing_client(global_args, network_details)?).await + } + _ => unreachable!() } Ok(()) }