Files
nym/contracts/vesting/src/contract.rs
T
Jędrzej Stuczyński cfcb64f7e5 Feature/reduce pledge (#3254)
* basic contract work for 'decrease_pledge' functionality

note: it doesn't yet return tokens back to the operator

* returning extra tokens after decreasing pledge

* added vesting message to track pledge decrease

* attaching the track message when processing delegation decrease

* checking for zero value request

* fixed event test

* allowing to decrease pledge from the vesting contract

* integration test for the feature

* reorganised the integration tests

* updated nyxd client traits

* wallet support

* typescript helpers

* moved 'pledge more' functionality to operator commands

* cli commands for decreasing pledge

* changed error variant to make clippy happier

* removed unused import

* eslint

* fixed post-rebase imports

* added cargo config

* added PendingMixNodeChanges to MixNodeDetails

* returning event id after creating it

* Streamlined getting mixnode details by identity key

* setting pending pledge changes on increase/decrease

* clearing the value on resolving the event

* checking for correct invariants when clearing events

* further pending events unit tests fixes

* new unit tests for tx endpoints

* queries for pending events (by id)

* migration code

* using default value for pending changes if unavailable

* improved integration test assertions
2023-04-20 07:52:10 +01:00

347 lines
13 KiB
Rust

use crate::errors::ContractError;
pub use crate::queries::*;
use crate::storage::{ADMIN, MIXNET_CONTRACT_ADDRESS, MIX_DENOM};
pub use crate::transactions::*;
use crate::vesting::Account;
use cosmwasm_std::{
entry_point, to_binary, Addr, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response,
Uint128,
};
use semver::Version;
use vesting_contract_common::messages::{ExecuteMsg, InitMsg, MigrateMsg, QueryMsg};
// version info for migration info
const CONTRACT_NAME: &str = "crate:nym-vesting-contract";
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
pub const INITIAL_LOCKED_PLEDGE_CAP: Uint128 = Uint128::new(100_000_000_000);
// as of 01.02.23 the maximum number of delegations anyone has made towards particular mixnode is 12.
// thus restricting it to 25, which is more than double of that, doesn't seem too unreasonable.
// and is going to alleviate the issue of unbounded iteration in `remove_delegations_for_mix`
// that happens upon advancing the current epoch.
//
// However, do note it doesn't necessarily mean that upon reaching this limit it's impossible to perform
// further delegations (towards the same node)
// while this might not be the best workaround, you could remove the existing delegation
// (thus removing all separate entries from the storage, i.e. the `DELEGATIONS` map)
// and re-delegate it with the reclaimed amount (which will include all rewards)
// which will only result in a single key-value being stored.
pub const MAX_PER_MIX_DELEGATIONS: u32 = 25;
/// Instantiate the contract
#[entry_point]
pub fn instantiate(
deps: DepsMut<'_>,
_env: Env,
info: MessageInfo,
msg: InitMsg,
) -> Result<Response, ContractError> {
// validate the received mixnet contract address
let mixnet_contract_address = deps.api.addr_validate(&msg.mixnet_contract_address)?;
// ADMIN is set to the address that instantiated the contract
ADMIN.save(deps.storage, &info.sender)?;
MIXNET_CONTRACT_ADDRESS.save(deps.storage, &mixnet_contract_address)?;
MIX_DENOM.save(deps.storage, &msg.mix_denom)?;
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
Ok(Response::default())
}
#[entry_point]
pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
// note: don't remove this particular bit of code as we have to ALWAYS check whether we have to update the stored version
let version: Version =
CONTRACT_VERSION
.parse()
.map_err(|error: semver::Error| ContractError::SemVerFailure {
value: CONTRACT_VERSION.to_string(),
error_message: error.to_string(),
})?;
let storage_version_raw = cw2::get_contract_version(deps.storage)?.version;
let storage_version: Version =
storage_version_raw
.parse()
.map_err(|error: semver::Error| ContractError::SemVerFailure {
value: storage_version_raw,
error_message: error.to_string(),
})?;
if storage_version < version {
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
// If state structure changed in any contract version in the way migration is needed, it
// should occur here, for example anything from `crate::queued_migrations::`
}
Ok(Response::new())
}
#[entry_point]
pub fn execute(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
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)
}
ExecuteMsg::TrackUndelegation {
owner,
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)
}
}
}
#[entry_point]
pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
let query_res = match msg {
QueryMsg::GetContractVersion {} => to_binary(&get_contract_version()),
QueryMsg::GetCW2ContractVersion {} => to_binary(&cw2::get_contract_version(deps.storage)?),
QueryMsg::GetAccountsPaged {
start_next_after,
limit,
} => to_binary(&try_get_all_accounts(deps, start_next_after, limit)?),
QueryMsg::GetAccountsVestingCoinsPaged {
start_next_after,
limit,
} => to_binary(&try_get_all_accounts_vesting_coins(
deps,
env,
start_next_after,
limit,
)?),
QueryMsg::LockedCoins {
vesting_account_address,
block_time,
} => to_binary(&try_get_locked_coins(
&vesting_account_address,
block_time,
env,
deps,
)?),
QueryMsg::SpendableCoins {
vesting_account_address,
block_time,
} => to_binary(&try_get_spendable_coins(
&vesting_account_address,
block_time,
env,
deps,
)?),
QueryMsg::GetVestedCoins {
vesting_account_address,
block_time,
} => to_binary(&try_get_vested_coins(
&vesting_account_address,
block_time,
env,
deps,
)?),
QueryMsg::GetVestingCoins {
vesting_account_address,
block_time,
} => to_binary(&try_get_vesting_coins(
&vesting_account_address,
block_time,
env,
deps,
)?),
QueryMsg::GetStartTime {
vesting_account_address,
} => to_binary(&try_get_start_time(&vesting_account_address, deps)?),
QueryMsg::GetEndTime {
vesting_account_address,
} => to_binary(&try_get_end_time(&vesting_account_address, deps)?),
QueryMsg::GetOriginalVesting {
vesting_account_address,
} => to_binary(&try_get_original_vesting(&vesting_account_address, deps)?),
QueryMsg::GetHistoricalVestingStakingReward {
vesting_account_address,
} => to_binary(&try_get_historical_vesting_staking_reward(
&vesting_account_address,
deps,
)?),
QueryMsg::GetSpendableVestedCoins {
vesting_account_address,
} => to_binary(&try_get_spendable_vested_coins(
&vesting_account_address,
deps,
env,
)?),
QueryMsg::GetSpendableRewardCoins {
vesting_account_address,
} => to_binary(&try_get_spendable_reward_coins(
&vesting_account_address,
deps,
env,
)?),
QueryMsg::GetDelegatedCoins {
vesting_account_address,
} => to_binary(&try_get_delegated_coins(&vesting_account_address, deps)?),
QueryMsg::GetPledgedCoins {
vesting_account_address,
} => to_binary(&try_get_pledged_coins(&vesting_account_address, deps)?),
QueryMsg::GetStakedCoins {
vesting_account_address,
} => to_binary(&try_get_staked_coins(&vesting_account_address, deps)?),
QueryMsg::GetWithdrawnCoins {
vesting_account_address,
} => to_binary(&try_get_withdrawn_coins(&vesting_account_address, deps)?),
QueryMsg::GetAccount { address } => to_binary(&try_get_account(&address, deps)?),
QueryMsg::GetMixnode { address } => to_binary(&try_get_mixnode(&address, deps)?),
QueryMsg::GetGateway { address } => to_binary(&try_get_gateway(&address, deps)?),
QueryMsg::GetCurrentVestingPeriod { address } => {
to_binary(&try_get_current_vesting_period(&address, deps, env)?)
}
QueryMsg::GetDelegation {
address,
mix_id,
block_timestamp_secs,
} => to_binary(&try_get_delegation(
deps,
&address,
mix_id,
block_timestamp_secs,
)?),
QueryMsg::GetTotalDelegationAmount { address, mix_id } => {
to_binary(&try_get_delegation_amount(deps, &address, mix_id)?)
}
QueryMsg::GetDelegationTimes { address, mix_id } => {
to_binary(&try_get_delegation_times(deps, &address, mix_id)?)
}
QueryMsg::GetAllDelegations { start_after, limit } => {
to_binary(&try_get_all_delegations(deps, start_after, limit)?)
}
};
Ok(query_res?)
}
pub(crate) fn validate_funds(funds: &[Coin], mix_denom: String) -> Result<Coin, ContractError> {
if funds.is_empty() || funds[0].amount.is_zero() {
return Err(ContractError::EmptyFunds);
}
if funds.len() > 1 {
return Err(ContractError::MultipleDenoms);
}
if funds[0].denom != mix_denom {
return Err(ContractError::WrongDenom(funds[0].denom.clone(), mix_denom));
}
Ok(funds[0].clone())
}
pub(crate) fn ensure_staking_permission(
addr: &Addr,
account: &Account,
) -> Result<(), ContractError> {
if let Some(staking_address) = account.staking_address() {
if staking_address == addr {
return Ok(());
}
}
Err(ContractError::InvalidStakingAccount {
address: addr.clone(),
for_account: account.owner_address(),
})
}