Feature/pledge more (#1679)
* New transactions for increasing amount of pledged tokens * unit tests * Added an option to pledge extra tokens through the vesting contract * Introduced wallet endpoints for new operations * Using updated pledge cap in the vesting contract * Changelog update
This commit is contained in:
committed by
GitHub
parent
52d06785fb
commit
033333bb52
@@ -180,6 +180,35 @@ pub trait MixnetSigningClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn pledge_more(
|
||||
&self,
|
||||
additional_pledge: Coin,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError> {
|
||||
self.execute_mixnet_contract(
|
||||
fee,
|
||||
MixnetExecuteMsg::PledgeMore {},
|
||||
vec![additional_pledge],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn pledge_more_on_behalf(
|
||||
&self,
|
||||
owner: AccountId,
|
||||
additional_pledge: Coin,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError> {
|
||||
self.execute_mixnet_contract(
|
||||
fee,
|
||||
MixnetExecuteMsg::PledgeMoreOnBehalf {
|
||||
owner: owner.to_string(),
|
||||
},
|
||||
vec![additional_pledge],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError> {
|
||||
self.execute_mixnet_contract(fee, MixnetExecuteMsg::UnbondMixnode {}, vec![])
|
||||
.await
|
||||
|
||||
@@ -64,6 +64,21 @@ pub trait VestingSigningClient {
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError>;
|
||||
|
||||
async fn vesting_pledge_more(
|
||||
&self,
|
||||
additional_pledge: Coin,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError> {
|
||||
self.execute_vesting_contract(
|
||||
fee,
|
||||
VestingExecuteMsg::PledgeMore {
|
||||
amount: additional_pledge.into(),
|
||||
},
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn vesting_unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError>;
|
||||
|
||||
async fn vesting_track_unbond_mixnode(
|
||||
|
||||
@@ -12,6 +12,8 @@ pub const EVENT_VERSION_PREFIX: &str = "v2_";
|
||||
|
||||
pub enum MixnetEventType {
|
||||
MixnodeBonding,
|
||||
PendingPledgeIncrease,
|
||||
PledgeIncrease,
|
||||
GatewayBonding,
|
||||
GatewayUnbonding,
|
||||
PendingMixnodeUnbonding,
|
||||
@@ -51,6 +53,8 @@ impl ToString for MixnetEventType {
|
||||
fn to_string(&self) -> String {
|
||||
let event_name = match self {
|
||||
MixnetEventType::MixnodeBonding => "mixnode_bonding",
|
||||
MixnetEventType::PendingPledgeIncrease => "pending_pledge_increase",
|
||||
MixnetEventType::PledgeIncrease => "pledge_increase",
|
||||
MixnetEventType::GatewayBonding => "gateway_bonding",
|
||||
MixnetEventType::GatewayUnbonding => "gateway_unbonding",
|
||||
MixnetEventType::PendingMixnodeUnbonding => "pending_mixnode_unbonding",
|
||||
@@ -330,6 +334,19 @@ pub fn new_mixnode_bonding_event(
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
}
|
||||
|
||||
pub fn new_pending_pledge_increase_event(mix_id: MixId, amount: &Coin) -> Event {
|
||||
Event::new(MixnetEventType::PendingPledgeIncrease)
|
||||
.add_attribute(MIX_ID_KEY, mix_id.to_string())
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
}
|
||||
|
||||
pub fn new_pledge_increase_event(created_at: BlockHeight, mix_id: MixId, amount: &Coin) -> Event {
|
||||
Event::new(MixnetEventType::PledgeIncrease)
|
||||
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
|
||||
.add_attribute(MIX_ID_KEY, mix_id.to_string())
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
}
|
||||
|
||||
pub fn new_mixnode_unbonding_event(created_at: BlockHeight, mix_id: MixId) -> Event {
|
||||
Event::new(MixnetEventType::MixnodeUnbonding)
|
||||
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
|
||||
|
||||
@@ -325,6 +325,14 @@ impl MixNodeRewarding {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn increase_operator_uint128(
|
||||
&mut self,
|
||||
amount: Uint128,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
self.operator += amount.into_base_decimal()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn increase_delegates_uint128(
|
||||
&mut self,
|
||||
amount: Uint128,
|
||||
|
||||
@@ -113,6 +113,10 @@ pub enum ExecuteMsg {
|
||||
owner_signature: String,
|
||||
owner: String,
|
||||
},
|
||||
PledgeMore {},
|
||||
PledgeMoreOnBehalf {
|
||||
owner: String,
|
||||
},
|
||||
UnbondMixnode {},
|
||||
UnbondMixnodeOnBehalf {
|
||||
owner: String,
|
||||
@@ -223,6 +227,8 @@ impl ExecuteMsg {
|
||||
ExecuteMsg::BondMixnodeOnBehalf { mix_node, .. } => {
|
||||
format!("bonding mixnode {} on behalf", mix_node.identity_key)
|
||||
}
|
||||
ExecuteMsg::PledgeMore {} => "pledging additional tokens".into(),
|
||||
ExecuteMsg::PledgeMoreOnBehalf { .. } => "pledging additional tokens on behalf".into(),
|
||||
ExecuteMsg::UnbondMixnode { .. } => "unbonding mixnode".into(),
|
||||
ExecuteMsg::UnbondMixnodeOnBehalf { .. } => "unbonding mixnode on behalf".into(),
|
||||
ExecuteMsg::UpdateMixnodeCostParams { .. } => "updating mixnode cost parameters".into(),
|
||||
|
||||
@@ -34,6 +34,10 @@ pub enum PendingEpochEventKind {
|
||||
mix_id: MixId,
|
||||
proxy: Option<Addr>,
|
||||
},
|
||||
PledgeMore {
|
||||
mix_id: MixId,
|
||||
amount: Coin,
|
||||
},
|
||||
UnbondMixnode {
|
||||
mix_id: MixId,
|
||||
},
|
||||
@@ -78,7 +82,6 @@ pub enum PendingIntervalEventKind {
|
||||
mix_id: MixId,
|
||||
new_costs: MixNodeCostParams,
|
||||
},
|
||||
|
||||
UpdateRewardingParams {
|
||||
update: IntervalRewardingParamsUpdate,
|
||||
},
|
||||
|
||||
@@ -14,6 +14,7 @@ pub const VESTING_UNDELEGATION_EVENT_TYPE: &str = "vesting_undelegation";
|
||||
pub const VESTING_GATEWAY_BONDING_EVENT_TYPE: &str = "vesting_gateway_bonding";
|
||||
pub const VESTING_GATEWAY_UNBONDING_EVENT_TYPE: &str = "vesting_gateway_unbonding";
|
||||
pub const VESTING_MIXNODE_BONDING_EVENT_TYPE: &str = "vesting_mixnode_bonding";
|
||||
pub const VESTING_PLEDGE_MORE_EVENT_TYPE: &str = "vesting_pledge_more";
|
||||
pub const VESTING_MIXNODE_UNBONDING_EVENT_TYPE: &str = "vesting_mixnode_unbonding";
|
||||
pub const VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE: &str = "vesting_update_mixnode_config";
|
||||
pub const VESTING_UPDATE_MIXNODE_COST_PARAMS_EVENT_TYPE: &str =
|
||||
@@ -112,6 +113,10 @@ pub fn new_vesting_mixnode_bonding_event() -> Event {
|
||||
Event::new(VESTING_MIXNODE_BONDING_EVENT_TYPE)
|
||||
}
|
||||
|
||||
pub fn new_vesting_pledge_more_event() -> Event {
|
||||
Event::new(VESTING_PLEDGE_MORE_EVENT_TYPE)
|
||||
}
|
||||
|
||||
pub fn new_vesting_update_mixnode_config_event() -> Event {
|
||||
Event::new(VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE)
|
||||
}
|
||||
|
||||
@@ -101,6 +101,9 @@ pub enum ExecuteMsg {
|
||||
owner_signature: String,
|
||||
amount: Coin,
|
||||
},
|
||||
PledgeMore {
|
||||
amount: Coin,
|
||||
},
|
||||
UnbondMixnode {},
|
||||
TrackUnbondMixnode {
|
||||
owner: String,
|
||||
@@ -145,6 +148,7 @@ impl ExecuteMsg {
|
||||
ExecuteMsg::WithdrawVestedCoins { .. } => "VestingExecuteMsg::WithdrawVestedCoins",
|
||||
ExecuteMsg::TrackUndelegation { .. } => "VestingExecuteMsg::TrackUndelegation",
|
||||
ExecuteMsg::BondMixnode { .. } => "VestingExecuteMsg::BondMixnode",
|
||||
ExecuteMsg::PledgeMore { .. } => "VestingExecuteMsg::PledgeMore",
|
||||
ExecuteMsg::UnbondMixnode { .. } => "VestingExecuteMsg::UnbondMixnode",
|
||||
ExecuteMsg::TrackUnbondMixnode { .. } => "VestingExecuteMsg::TrackUnbondMixnode",
|
||||
ExecuteMsg::BondGateway { .. } => "VestingExecuteMsg::BondGateway",
|
||||
|
||||
@@ -57,6 +57,10 @@ pub enum PendingEpochEventData {
|
||||
mix_id: MixId,
|
||||
proxy: Option<String>,
|
||||
},
|
||||
PledgeMore {
|
||||
mix_id: MixId,
|
||||
amount: DecCoin,
|
||||
},
|
||||
UnbondMixnode {
|
||||
mix_id: MixId,
|
||||
},
|
||||
@@ -91,6 +95,12 @@ impl PendingEpochEventData {
|
||||
mix_id,
|
||||
proxy: proxy.map(|p| p.into_string()),
|
||||
}),
|
||||
MixnetContractPendingEpochEventKind::PledgeMore { mix_id, amount } => {
|
||||
Ok(PendingEpochEventData::PledgeMore {
|
||||
mix_id,
|
||||
amount: reg.attempt_convert_to_display_dec_coin(amount.into())?,
|
||||
})
|
||||
}
|
||||
MixnetContractPendingEpochEventKind::UnbondMixnode { mix_id } => {
|
||||
Ok(PendingEpochEventData::UnbondMixnode { mix_id })
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
## Added
|
||||
|
||||
- Added migration code to the mixnet contract to allow updating stored vesting contract address to make it easier to deploy any future environments ([#1759],[#1769])
|
||||
- Added an option to pledge additional tokens without the need to rebond minxode ([#1679])
|
||||
|
||||
[#1679]: https://github.com/nymtech/nym/pull/1679
|
||||
[#1759]: https://github.com/nymtech/nym/pull/1759
|
||||
[#1769]: https://github.com/nymtech/nym/pull/1769
|
||||
|
||||
|
||||
@@ -168,6 +168,12 @@ pub fn execute(
|
||||
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::UnbondMixnode {} => {
|
||||
crate::mixnodes::transactions::try_remove_mixnode(deps, env, info)
|
||||
}
|
||||
|
||||
@@ -7,13 +7,14 @@ use crate::interval::helpers::change_interval_config;
|
||||
use crate::interval::storage;
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_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;
|
||||
use cosmwasm_std::{wasm_execute, Addr, Coin, DepsMut, Env, Response};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::events::{
|
||||
new_active_set_update_event, new_delegation_event, new_delegation_on_unbonded_node_event,
|
||||
new_mixnode_cost_params_update_event, new_mixnode_unbonding_event,
|
||||
new_mixnode_cost_params_update_event, new_mixnode_unbonding_event, new_pledge_increase_event,
|
||||
new_rewarding_params_update_event, new_undelegation_event,
|
||||
};
|
||||
use mixnet_contract_common::mixnode::MixNodeCostParams;
|
||||
@@ -258,6 +259,42 @@ pub(crate) fn update_active_set_size(
|
||||
Ok(Response::new().add_event(new_active_set_update_event(created_at, active_set_size)))
|
||||
}
|
||||
|
||||
pub(crate) fn increase_pledge(
|
||||
deps: DepsMut<'_>,
|
||||
created_at: BlockHeight,
|
||||
mix_id: MixId,
|
||||
increase: Coin,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
// note: we have already validated the amount to know it has the correct denomination
|
||||
|
||||
// the target node MUST exist - we have checked it at the time of putting this event onto the queue
|
||||
// we have also verified there were no preceding unbond events
|
||||
let mix_details = get_mixnode_details_by_id(deps.storage, mix_id)?.ok_or(
|
||||
MixnetContractError::InconsistentState {
|
||||
comment:
|
||||
"mixnode getting processed to increase its pledge doesn't exist in the storage"
|
||||
.into(),
|
||||
},
|
||||
)?;
|
||||
|
||||
let mut updated_bond = mix_details.bond_information.clone();
|
||||
let mut updated_rewarding = mix_details.rewarding_details;
|
||||
|
||||
updated_bond.original_pledge.amount += increase.amount;
|
||||
updated_rewarding.increase_operator_uint128(increase.amount)?;
|
||||
|
||||
// update both, bond information and rewarding details
|
||||
mixnodes_storage::mixnode_bonds().replace(
|
||||
deps.storage,
|
||||
mix_id,
|
||||
Some(&updated_bond),
|
||||
Some(&mix_details.bond_information),
|
||||
)?;
|
||||
rewards_storage::MIXNODE_REWARDING.save(deps.storage, mix_id, &updated_rewarding)?;
|
||||
|
||||
Ok(Response::new().add_event(new_pledge_increase_event(created_at, mix_id, &increase)))
|
||||
}
|
||||
|
||||
impl ContractExecutableEvent for PendingEpochEventData {
|
||||
fn execute(self, deps: DepsMut<'_>, env: &Env) -> Result<Response, MixnetContractError> {
|
||||
// note that the basic validation on all those events was already performed before
|
||||
@@ -274,6 +311,9 @@ impl ContractExecutableEvent for PendingEpochEventData {
|
||||
mix_id,
|
||||
proxy,
|
||||
} => undelegate(deps, self.created_at, owner, mix_id, proxy),
|
||||
PendingEpochEventKind::PledgeMore { mix_id, amount } => {
|
||||
increase_pledge(deps, self.created_at, mix_id, amount)
|
||||
}
|
||||
PendingEpochEventKind::UnbondMixnode { mix_id } => {
|
||||
unbond_mixnode(deps, env, self.created_at, mix_id)
|
||||
}
|
||||
@@ -1135,6 +1175,228 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod increasing_pledge {
|
||||
use super::*;
|
||||
use cosmwasm_std::Uint128;
|
||||
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
|
||||
|
||||
#[test]
|
||||
fn returns_hard_error_if_mixnode_doesnt_exist() {
|
||||
// this should have never happened so hard error MUST be thrown here
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let amount = test.coin(123);
|
||||
let res = increase_pledge(test.deps_mut(), 123, 1, amount);
|
||||
assert!(matches!(
|
||||
res,
|
||||
Err(MixnetContractError::InconsistentState { .. })
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn updates_stored_bond_information_and_rewarding_details() {
|
||||
let mut test = TestSetup::new();
|
||||
let mix_id = test.add_dummy_mixnode("mix-owner", None);
|
||||
|
||||
let old_details = get_mixnode_details_by_id(test.deps().storage, mix_id)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
let amount = test.coin(12345);
|
||||
increase_pledge(test.deps_mut(), 123, mix_id, amount.clone()).unwrap();
|
||||
|
||||
let updated_details = get_mixnode_details_by_id(test.deps().storage, mix_id)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
updated_details.bond_information.original_pledge.amount,
|
||||
old_details.bond_information.original_pledge.amount + amount.amount
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
updated_details.rewarding_details.operator,
|
||||
old_details.rewarding_details.operator
|
||||
+ Decimal::from_atomics(amount.amount, 0).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn without_any_events_in_between_is_equivalent_to_pledging_the_same_amount_immediately() {
|
||||
let mut test = TestSetup::new();
|
||||
let pledge1 = Uint128::new(150_000_000);
|
||||
let pledge2 = Uint128::new(50_000_000);
|
||||
let pledge3 = Uint128::new(200_000_000);
|
||||
|
||||
let mix_id_repledge = test.add_dummy_mixnode("mix-owner1", Some(pledge1));
|
||||
let increase = test.coin(pledge2.u128());
|
||||
increase_pledge(test.deps_mut(), 123, mix_id_repledge, increase).unwrap();
|
||||
|
||||
let mix_id_full_pledge = test.add_dummy_mixnode("mix-owner2", Some(pledge3));
|
||||
|
||||
test.add_immediate_delegation("alice", 123_456_789u128, mix_id_repledge);
|
||||
test.add_immediate_delegation("bob", 500_000_000u128, mix_id_repledge);
|
||||
test.add_immediate_delegation("carol", 111_111_111u128, mix_id_repledge);
|
||||
|
||||
test.add_immediate_delegation("alice", 123_456_789u128, mix_id_full_pledge);
|
||||
test.add_immediate_delegation("bob", 500_000_000u128, mix_id_full_pledge);
|
||||
test.add_immediate_delegation("carol", 111_111_111u128, mix_id_full_pledge);
|
||||
|
||||
test.skip_to_next_epoch_end();
|
||||
test.update_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]);
|
||||
|
||||
let dist1 =
|
||||
test.reward_with_distribution(mix_id_repledge, test_helpers::performance(100.0));
|
||||
let dist2 =
|
||||
test.reward_with_distribution(mix_id_full_pledge, test_helpers::performance(100.0));
|
||||
|
||||
assert_eq!(dist1, dist2)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correctly_increases_future_rewards() {
|
||||
let mut test = TestSetup::new();
|
||||
let pledge1 = Uint128::new(150_000_000_000);
|
||||
let pledge2 = Uint128::new(50_000_000_000);
|
||||
|
||||
let mix_id_repledge = test.add_dummy_mixnode("mix-owner1", Some(pledge1));
|
||||
|
||||
test.add_immediate_delegation("alice", 123_456_789_000u128, mix_id_repledge);
|
||||
test.add_immediate_delegation("bob", 500_000_000_000u128, mix_id_repledge);
|
||||
test.add_immediate_delegation("carol", 111_111_111_000u128, mix_id_repledge);
|
||||
|
||||
test.skip_to_next_epoch_end();
|
||||
test.update_rewarded_set(vec![mix_id_repledge]);
|
||||
|
||||
let dist =
|
||||
test.reward_with_distribution(mix_id_repledge, test_helpers::performance(100.0));
|
||||
|
||||
let increase = test.coin(pledge2.u128());
|
||||
increase_pledge(test.deps_mut(), 123, mix_id_repledge, increase).unwrap();
|
||||
|
||||
let pledge3 = Uint128::new(200_000_000_000) + truncate_reward_amount(dist.operator);
|
||||
let mix_id_full_pledge = test.add_dummy_mixnode("mix-owner2", Some(pledge3));
|
||||
|
||||
test.add_immediate_delegation("alice", 123_456_789_000u128, mix_id_full_pledge);
|
||||
test.add_immediate_delegation("bob", 500_000_000_000u128, mix_id_full_pledge);
|
||||
test.add_immediate_delegation("carol", 111_111_111_000u128, mix_id_full_pledge);
|
||||
|
||||
let lost_operator = dist.operator
|
||||
- Decimal::from_atomics(truncate_reward_amount(dist.operator), 0).unwrap();
|
||||
let lost_delegates = dist.delegates
|
||||
- Decimal::from_atomics(truncate_reward_amount(dist.delegates), 0).unwrap();
|
||||
|
||||
// add the tiny bit of lost precision manually
|
||||
let mut mix_rewarding_full = test.mix_rewarding(mix_id_full_pledge);
|
||||
mix_rewarding_full.delegates += lost_delegates;
|
||||
mix_rewarding_full.operator += lost_operator;
|
||||
rewards_storage::MIXNODE_REWARDING
|
||||
.save(
|
||||
test.deps_mut().storage,
|
||||
mix_id_full_pledge,
|
||||
&mix_rewarding_full,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
test.add_immediate_delegation(
|
||||
"dave",
|
||||
truncate_reward_amount(dist.delegates).u128(),
|
||||
mix_id_full_pledge,
|
||||
);
|
||||
|
||||
test.skip_to_next_epoch_end();
|
||||
test.update_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]);
|
||||
|
||||
// go through few epochs of rewarding
|
||||
for _ in 0..500 {
|
||||
test.skip_to_next_epoch_end();
|
||||
let dist1 = test
|
||||
.reward_with_distribution(mix_id_repledge, test_helpers::performance(100.0));
|
||||
let dist2 = test
|
||||
.reward_with_distribution(mix_id_full_pledge, test_helpers::performance(100.0));
|
||||
|
||||
assert_eq!(dist1, dist2)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correctly_increases_future_rewards_with_more_passed_epochs() {
|
||||
let mut test = TestSetup::new();
|
||||
let pledge1 = Uint128::new(150_000_000_000);
|
||||
let pledge2 = Uint128::new(50_000_000_000);
|
||||
|
||||
let mix_id_repledge = test.add_dummy_mixnode("mix-owner1", Some(pledge1));
|
||||
|
||||
test.add_immediate_delegation("alice", 123_456_789_000u128, mix_id_repledge);
|
||||
test.add_immediate_delegation("bob", 500_000_000_000u128, mix_id_repledge);
|
||||
test.add_immediate_delegation("carol", 111_111_111_000u128, mix_id_repledge);
|
||||
|
||||
test.skip_to_next_epoch_end();
|
||||
test.update_rewarded_set(vec![mix_id_repledge]);
|
||||
|
||||
let mut cumulative_op_reward = Decimal::zero();
|
||||
let mut cumulative_del_reward = Decimal::zero();
|
||||
|
||||
// go few epochs of rewarding before adding more pledge
|
||||
for _ in 0..500 {
|
||||
test.skip_to_next_epoch_end();
|
||||
let dist = test
|
||||
.reward_with_distribution(mix_id_repledge, test_helpers::performance(100.0));
|
||||
cumulative_op_reward += dist.operator;
|
||||
cumulative_del_reward += dist.delegates;
|
||||
}
|
||||
|
||||
let increase = test.coin(pledge2.u128());
|
||||
increase_pledge(test.deps_mut(), 123, mix_id_repledge, increase).unwrap();
|
||||
|
||||
let pledge3 =
|
||||
Uint128::new(200_000_000_000) + truncate_reward_amount(cumulative_op_reward);
|
||||
let mix_id_full_pledge = test.add_dummy_mixnode("mix-owner2", Some(pledge3));
|
||||
|
||||
test.add_immediate_delegation("alice", 123_456_789_000u128, mix_id_full_pledge);
|
||||
test.add_immediate_delegation("bob", 500_000_000_000u128, mix_id_full_pledge);
|
||||
test.add_immediate_delegation("carol", 111_111_111_000u128, mix_id_full_pledge);
|
||||
|
||||
let lost_operator = cumulative_op_reward
|
||||
- Decimal::from_atomics(truncate_reward_amount(cumulative_op_reward), 0).unwrap();
|
||||
let lost_delegates = cumulative_del_reward
|
||||
- Decimal::from_atomics(truncate_reward_amount(cumulative_del_reward), 0).unwrap();
|
||||
|
||||
// add the tiny bit of lost precision manually
|
||||
let mut mix_rewarding_full = test.mix_rewarding(mix_id_full_pledge);
|
||||
mix_rewarding_full.delegates += lost_delegates;
|
||||
mix_rewarding_full.operator += lost_operator;
|
||||
rewards_storage::MIXNODE_REWARDING
|
||||
.save(
|
||||
test.deps_mut().storage,
|
||||
mix_id_full_pledge,
|
||||
&mix_rewarding_full,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
test.add_immediate_delegation(
|
||||
"dave",
|
||||
truncate_reward_amount(cumulative_del_reward).u128(),
|
||||
mix_id_full_pledge,
|
||||
);
|
||||
|
||||
test.skip_to_next_epoch_end();
|
||||
test.update_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]);
|
||||
|
||||
// go through few more epochs of rewarding
|
||||
for _ in 0..500 {
|
||||
test.skip_to_next_epoch_end();
|
||||
let dist1 = test
|
||||
.reward_with_distribution(mix_id_repledge, test_helpers::performance(100.0));
|
||||
let dist2 = test
|
||||
.reward_with_distribution(mix_id_full_pledge, test_helpers::performance(100.0));
|
||||
|
||||
assert_eq!(dist1, dist2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn updating_active_set_updates_rewarding_params() {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
@@ -37,7 +37,6 @@ pub(crate) fn minimum_delegation_stake(
|
||||
.map(|state| state.params.minimum_mixnode_delegation)?)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn rewarding_denom(storage: &dyn Storage) -> Result<String, MixnetContractError> {
|
||||
Ok(CONTRACT_STATE
|
||||
.load(storage)
|
||||
|
||||
@@ -147,7 +147,7 @@ pub(crate) fn cleanup_post_unbond_mixnode_storage(
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::support::tests::fixtures::{
|
||||
mix_node_cost_params_fixture, mix_node_fixture, TEST_COIN_DENOM,
|
||||
@@ -155,13 +155,13 @@ mod tests {
|
||||
use crate::support::tests::test_helpers::TestSetup;
|
||||
use cosmwasm_std::coin;
|
||||
|
||||
const OWNER_EXISTS: &str = "mix-owner-existing";
|
||||
const OWNER_UNBONDING: &str = "mix-owner-unbonding";
|
||||
const OWNER_UNBONDED: &str = "mix-owner-unbonded";
|
||||
const OWNER_UNBONDED_LEFTOVER: &str = "mix-owner-unbonded-leftover";
|
||||
pub(crate) const OWNER_EXISTS: &str = "mix-owner-existing";
|
||||
pub(crate) const OWNER_UNBONDING: &str = "mix-owner-unbonding";
|
||||
pub(crate) const OWNER_UNBONDED: &str = "mix-owner-unbonded";
|
||||
pub(crate) const OWNER_UNBONDED_LEFTOVER: &str = "mix-owner-unbonded-leftover";
|
||||
|
||||
// create a mixnode that is bonded, unbonded, in the process of unbonding and unbonded with leftover mix rewarding details
|
||||
fn setup_mix_combinations(test: &mut TestSetup) -> Vec<MixId> {
|
||||
pub(crate) fn setup_mix_combinations(test: &mut TestSetup) -> Vec<MixId> {
|
||||
let mix_id_exists = test.add_dummy_mixnode(OWNER_EXISTS, None);
|
||||
let mix_id_unbonding = test.add_dummy_mixnode(OWNER_UNBONDING, None);
|
||||
let mix_id_unbonded = test.add_dummy_mixnode(OWNER_UNBONDED, None);
|
||||
|
||||
@@ -5,16 +5,20 @@ use super::storage;
|
||||
use crate::interval::storage as interval_storage;
|
||||
use crate::interval::storage::push_new_interval_event;
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||
use crate::mixnodes::helpers::{must_get_mixnode_bond_by_owner, save_new_mixnode};
|
||||
use crate::mixnet_contract_settings::storage::rewarding_denom;
|
||||
use crate::mixnodes::helpers::{
|
||||
get_mixnode_details_by_owner, must_get_mixnode_bond_by_owner, save_new_mixnode,
|
||||
};
|
||||
use crate::support::helpers::{
|
||||
ensure_bonded, ensure_no_existing_bond, ensure_proxy_match, validate_node_identity_signature,
|
||||
validate_pledge,
|
||||
};
|
||||
use cosmwasm_std::{Addr, Coin, DepsMut, Env, MessageInfo, Response};
|
||||
use cosmwasm_std::{coin, Addr, Coin, DepsMut, Env, MessageInfo, Response};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::events::{
|
||||
new_mixnode_bonding_event, new_mixnode_config_update_event,
|
||||
new_mixnode_pending_cost_params_update_event, new_pending_mixnode_unbonding_event,
|
||||
new_pending_pledge_increase_event,
|
||||
};
|
||||
use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
|
||||
use mixnet_contract_common::pending_events::{PendingEpochEventKind, PendingIntervalEventKind};
|
||||
@@ -117,6 +121,54 @@ fn _try_add_mixnode(
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn try_increase_pledge(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_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<Response, MixnetContractError> {
|
||||
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<Coin>,
|
||||
owner: Addr,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())?
|
||||
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })?;
|
||||
let mix_id = mix_details.mix_id();
|
||||
|
||||
ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?;
|
||||
ensure_bonded(&mix_details.bond_information)?;
|
||||
|
||||
let rewarding_denom = rewarding_denom(deps.storage)?;
|
||||
let pledge_increase = validate_pledge(increase, coin(1, rewarding_denom))?;
|
||||
|
||||
let cosmos_event = new_pending_pledge_increase_event(mix_id, &pledge_increase);
|
||||
|
||||
// push the event to execute it at the end of the epoch
|
||||
let epoch_event = PendingEpochEventKind::PledgeMore {
|
||||
mix_id,
|
||||
amount: pledge_increase,
|
||||
};
|
||||
interval_storage::push_new_epoch_event(deps.storage, &env, epoch_event)?;
|
||||
|
||||
Ok(Response::new().add_event(cosmos_event))
|
||||
}
|
||||
|
||||
pub fn try_remove_mixnode_on_behalf(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
@@ -680,4 +732,187 @@ pub mod tests {
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod increasing_mixnode_pledge {
|
||||
use super::*;
|
||||
use crate::mixnodes::helpers::tests::{
|
||||
setup_mix_combinations, OWNER_UNBONDED, OWNER_UNBONDED_LEFTOVER, OWNER_UNBONDING,
|
||||
};
|
||||
use crate::support::tests::test_helpers::TestSetup;
|
||||
|
||||
#[test]
|
||||
fn is_not_allowed_if_account_doesnt_own_mixnode() {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
let sender = mock_info("not-mix-owner", &[]);
|
||||
|
||||
let res = try_increase_pledge(test.deps_mut(), env, sender);
|
||||
assert_eq!(
|
||||
res,
|
||||
Err(MixnetContractError::NoAssociatedMixNodeBond {
|
||||
owner: Addr::unchecked("not-mix-owner")
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
#[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_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();
|
||||
let env = test.env();
|
||||
|
||||
// TODO: I dislike this cross-test access, but it provides us with exactly what we need
|
||||
// perhaps it should be refactored a bit?
|
||||
let owner_unbonding = Addr::unchecked(OWNER_UNBONDING);
|
||||
let owner_unbonded = Addr::unchecked(OWNER_UNBONDED);
|
||||
let owner_unbonded_leftover = Addr::unchecked(OWNER_UNBONDED_LEFTOVER);
|
||||
|
||||
let ids = setup_mix_combinations(&mut test);
|
||||
let mix_id_unbonding = ids[1];
|
||||
|
||||
let res = try_increase_pledge(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
mock_info(owner_unbonding.as_str(), &[]),
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
Err(MixnetContractError::MixnodeIsUnbonding {
|
||||
mix_id: mix_id_unbonding
|
||||
})
|
||||
);
|
||||
|
||||
// if the nodes are gone we treat them as tey never existed in the first place
|
||||
// (regardless of if there's some leftover data)
|
||||
let res = try_increase_pledge(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
mock_info(owner_unbonded_leftover.as_str(), &[]),
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
Err(MixnetContractError::NoAssociatedMixNodeBond {
|
||||
owner: owner_unbonded_leftover
|
||||
})
|
||||
);
|
||||
|
||||
let res = try_increase_pledge(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
mock_info(owner_unbonded.as_str(), &[]),
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
Err(MixnetContractError::NoAssociatedMixNodeBond {
|
||||
owner: owner_unbonded
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_not_allowed_if_no_tokens_were_sent() {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
let owner = "mix-owner";
|
||||
|
||||
test.add_dummy_mixnode(owner, None);
|
||||
|
||||
let sender_empty = mock_info(owner, &[]);
|
||||
let res = try_increase_pledge(test.deps_mut(), env.clone(), sender_empty);
|
||||
assert_eq!(res, Err(MixnetContractError::NoBondFound));
|
||||
|
||||
let sender_zero = mock_info(owner, &[test.coin(0)]);
|
||||
let res = try_increase_pledge(test.deps_mut(), env, sender_zero);
|
||||
assert_eq!(
|
||||
res,
|
||||
Err(MixnetContractError::InsufficientPledge {
|
||||
received: test.coin(0),
|
||||
minimum: test.coin(1)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_valid_information_creates_pending_event() {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
let owner = "mix-owner";
|
||||
let mix_id = test.add_dummy_mixnode(owner, None);
|
||||
|
||||
let events = test.pending_epoch_events();
|
||||
assert!(events.is_empty());
|
||||
|
||||
let sender = mock_info(owner, &[test.coin(1000)]);
|
||||
try_increase_pledge(test.deps_mut(), env, sender).unwrap();
|
||||
|
||||
let events = test.pending_epoch_events();
|
||||
|
||||
assert_eq!(
|
||||
events[0].kind,
|
||||
PendingEpochEventKind::PledgeMore {
|
||||
mix_id,
|
||||
amount: test.coin(1000)
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,9 @@ pub mod test_helpers {
|
||||
use crate::mixnodes::transactions::{
|
||||
try_add_mixnode, try_add_mixnode_on_behalf, try_remove_mixnode,
|
||||
};
|
||||
use crate::rewards::queries::{
|
||||
query_pending_delegator_reward, query_pending_mixnode_operator_reward,
|
||||
};
|
||||
use crate::rewards::storage as rewards_storage;
|
||||
use crate::rewards::transactions::try_reward_mixnode;
|
||||
use crate::support::tests;
|
||||
@@ -37,7 +40,7 @@ pub mod test_helpers {
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use cosmwasm_std::testing::MockApi;
|
||||
use cosmwasm_std::testing::MockQuerier;
|
||||
use cosmwasm_std::{Addr, BankMsg, CosmosMsg, Storage};
|
||||
use cosmwasm_std::{coin, Addr, BankMsg, CosmosMsg, Storage};
|
||||
use cosmwasm_std::{Coin, Order};
|
||||
use cosmwasm_std::{Decimal, Empty, MemoryStorage};
|
||||
use cosmwasm_std::{Deps, OwnedDeps};
|
||||
@@ -138,6 +141,10 @@ pub mod test_helpers {
|
||||
.vesting_contract_address
|
||||
}
|
||||
|
||||
pub fn coin(&self, amount: u128) -> Coin {
|
||||
coin(amount, rewarding_denom(self.deps().storage).unwrap())
|
||||
}
|
||||
|
||||
pub fn current_interval(&self) -> Interval {
|
||||
interval_storage::current_interval(self.deps().storage).unwrap()
|
||||
}
|
||||
@@ -308,6 +315,22 @@ pub mod test_helpers {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn pending_operator_reward(&mut self, mix: MixId) -> Decimal {
|
||||
query_pending_mixnode_operator_reward(self.deps(), mix)
|
||||
.unwrap()
|
||||
.amount_earned_detailed
|
||||
.expect("no reward!")
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn pending_delegator_reward(&mut self, delegator: &str, target: MixId) -> Decimal {
|
||||
query_pending_delegator_reward(self.deps(), delegator.into(), target, None)
|
||||
.unwrap()
|
||||
.amount_earned_detailed
|
||||
.expect("no reward!")
|
||||
}
|
||||
|
||||
pub fn skip_to_next_epoch_end(&mut self) {
|
||||
self.skip_to_next_epoch();
|
||||
self.skip_to_current_epoch_end();
|
||||
@@ -337,8 +360,19 @@ pub mod test_helpers {
|
||||
self.env.block.time = Timestamp::from_seconds(epoch_end as u64 + 1);
|
||||
let advanced = interval.advance_epoch();
|
||||
|
||||
if interval.current_epoch_id() != interval.epochs_in_interval() {
|
||||
assert_eq!(
|
||||
interval.current_epoch_absolute_id() + 1,
|
||||
advanced.current_epoch_absolute_id()
|
||||
);
|
||||
|
||||
if interval.current_epoch_id() != interval.epochs_in_interval() - 1 {
|
||||
assert_eq!(interval.current_epoch_id() + 1, advanced.current_epoch_id())
|
||||
} else {
|
||||
assert_eq!(advanced.current_epoch_id(), 0);
|
||||
assert_eq!(
|
||||
interval.current_interval_id() + 1,
|
||||
advanced.current_interval_id()
|
||||
)
|
||||
}
|
||||
|
||||
interval_storage::save_interval(self.deps_mut().storage, &advanced).unwrap()
|
||||
|
||||
@@ -121,6 +121,7 @@ pub fn execute(
|
||||
env,
|
||||
deps,
|
||||
),
|
||||
ExecuteMsg::PledgeMore { amount } => try_pledge_more(deps, env, info, amount),
|
||||
ExecuteMsg::UnbondMixnode {} => try_unbond_mixnode(info, deps),
|
||||
ExecuteMsg::TrackUnbondMixnode { owner, amount } => {
|
||||
try_track_unbond_mixnode(&owner, amount, info, deps)
|
||||
@@ -331,6 +332,19 @@ pub fn try_bond_mixnode(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn try_pledge_more(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
amount: Coin,
|
||||
) -> Result<Response, ContractError> {
|
||||
let mix_denom = MIX_DENOM.load(deps.storage)?;
|
||||
let additional_pledge = validate_funds(&[amount], mix_denom)?;
|
||||
|
||||
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||
account.try_pledge_additional_tokens(additional_pledge, &env, deps.storage)
|
||||
}
|
||||
|
||||
/// Unbond a mixnode, sends [mixnet_contract_common::ExecuteMsg::UnbondMixnodeOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
||||
pub fn try_unbond_mixnode(info: MessageInfo, deps: DepsMut<'_>) -> Result<Response, ContractError> {
|
||||
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||
|
||||
@@ -18,6 +18,13 @@ pub trait MixnodeBondingAccount {
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Response, ContractError>;
|
||||
|
||||
fn try_pledge_additional_tokens(
|
||||
&self,
|
||||
additional_pledge: Coin,
|
||||
env: &Env,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Response, ContractError>;
|
||||
|
||||
fn try_unbond_mixnode(&self, storage: &dyn Storage) -> Result<Response, ContractError>;
|
||||
|
||||
fn try_track_unbond_mixnode(
|
||||
|
||||
@@ -12,7 +12,8 @@ use mixnet_contract_common::mixnode::MixNodeCostParams;
|
||||
use mixnet_contract_common::{ExecuteMsg as MixnetExecuteMsg, MixNode};
|
||||
use vesting_contract_common::events::{
|
||||
new_vesting_mixnode_bonding_event, new_vesting_mixnode_unbonding_event,
|
||||
new_vesting_update_mixnode_config_event, new_vesting_update_mixnode_cost_params_event,
|
||||
new_vesting_pledge_more_event, new_vesting_update_mixnode_config_event,
|
||||
new_vesting_update_mixnode_cost_params_event,
|
||||
};
|
||||
use vesting_contract_common::PledgeData;
|
||||
|
||||
@@ -84,6 +85,64 @@ impl MixnodeBondingAccount for Account {
|
||||
.add_event(new_vesting_mixnode_bonding_event()))
|
||||
}
|
||||
|
||||
fn try_pledge_additional_tokens(
|
||||
&self,
|
||||
additional_pledge: Coin,
|
||||
env: &Env,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Response, ContractError> {
|
||||
let current_balance = self.load_balance(storage)?;
|
||||
let total_pledged_after =
|
||||
self.total_pledged_locked(storage, env)? + additional_pledge.amount;
|
||||
let locked_pledge_cap = self.absolute_pledge_cap()?;
|
||||
|
||||
if locked_pledge_cap < total_pledged_after {
|
||||
return Err(ContractError::LockedPledgeCapReached {
|
||||
current: total_pledged_after,
|
||||
cap: locked_pledge_cap,
|
||||
});
|
||||
}
|
||||
|
||||
if current_balance < additional_pledge.amount {
|
||||
return Err(ContractError::InsufficientBalance(
|
||||
self.owner_address().as_str().to_string(),
|
||||
current_balance.u128(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut pledge_data = if let Some(pledge_data) = self.load_mixnode_pledge(storage)? {
|
||||
pledge_data
|
||||
} else {
|
||||
return Err(ContractError::NoBondFound(
|
||||
self.owner_address().as_str().to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
// need a second pair of eyes here to make sure updating existing timestamp on pledge data
|
||||
// is not going to have some unexpected consequences
|
||||
pledge_data.amount.amount += additional_pledge.amount;
|
||||
pledge_data.block_time = env.block.time;
|
||||
|
||||
let msg = MixnetExecuteMsg::PledgeMoreOnBehalf {
|
||||
owner: self.owner_address().into_string(),
|
||||
};
|
||||
|
||||
let new_balance = Uint128::new(current_balance.u128() - additional_pledge.amount.u128());
|
||||
|
||||
let pledge_more_mag = wasm_execute(
|
||||
MIXNET_CONTRACT_ADDRESS.load(storage)?,
|
||||
&msg,
|
||||
vec![additional_pledge],
|
||||
)?;
|
||||
|
||||
self.save_balance(new_balance, storage)?;
|
||||
self.save_mixnode_pledge(pledge_data, storage)?;
|
||||
|
||||
Ok(Response::new()
|
||||
.add_message(pledge_more_mag)
|
||||
.add_event(new_vesting_pledge_more_event()))
|
||||
}
|
||||
|
||||
fn try_unbond_mixnode(&self, storage: &dyn Storage) -> Result<Response, ContractError> {
|
||||
let msg = MixnetExecuteMsg::UnbondMixnodeOnBehalf {
|
||||
owner: self.owner_address().into_string(),
|
||||
|
||||
@@ -55,6 +55,7 @@ fn main() {
|
||||
mixnet::admin::update_contract_settings,
|
||||
mixnet::bond::bond_gateway,
|
||||
mixnet::bond::bond_mixnode,
|
||||
mixnet::bond::pledge_more,
|
||||
mixnet::bond::gateway_bond_details,
|
||||
mixnet::bond::get_pending_operator_rewards,
|
||||
mixnet::bond::mixnode_bond_details,
|
||||
@@ -105,6 +106,7 @@ fn main() {
|
||||
vesting::rewards::vesting_claim_operator_reward,
|
||||
vesting::bond::vesting_bond_gateway,
|
||||
vesting::bond::vesting_bond_mixnode,
|
||||
vesting::bond::vesting_pledge_more,
|
||||
vesting::bond::vesting_unbond_gateway,
|
||||
vesting::bond::vesting_unbond_mixnode,
|
||||
vesting::bond::vesting_update_mixnode_cost_params,
|
||||
@@ -130,6 +132,7 @@ fn main() {
|
||||
simulate::mixnet::simulate_bond_gateway,
|
||||
simulate::mixnet::simulate_unbond_gateway,
|
||||
simulate::mixnet::simulate_bond_mixnode,
|
||||
simulate::mixnet::simulate_pledge_more,
|
||||
simulate::mixnet::simulate_unbond_mixnode,
|
||||
simulate::mixnet::simulate_update_mixnode_config,
|
||||
simulate::mixnet::simulate_update_mixnode_cost_params,
|
||||
@@ -140,6 +143,7 @@ fn main() {
|
||||
simulate::vesting::simulate_vesting_bond_gateway,
|
||||
simulate::vesting::simulate_vesting_unbond_gateway,
|
||||
simulate::vesting::simulate_vesting_bond_mixnode,
|
||||
simulate::vesting::simulate_vesting_pledge_more,
|
||||
simulate::vesting::simulate_vesting_unbond_mixnode,
|
||||
simulate::vesting::simulate_vesting_update_mixnode_config,
|
||||
simulate::vesting::simulate_vesting_update_mixnode_cost_params,
|
||||
|
||||
@@ -102,6 +102,33 @@ pub async fn bond_mixnode(
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn pledge_more(
|
||||
fee: Option<Fee>,
|
||||
additional_pledge: DecCoin,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let additional_pledge_base = guard.attempt_convert_to_base_coin(additional_pledge.clone())?;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
log::info!(
|
||||
">>> Pledge more, additional_pledge_display = {}, additional_pledge_base = {}, fee = {:?}",
|
||||
additional_pledge,
|
||||
additional_pledge_base,
|
||||
fee,
|
||||
);
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.pledge_more(additional_pledge_base, fee)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn unbond_mixnode(
|
||||
fee: Option<Fee>,
|
||||
|
||||
@@ -84,6 +84,14 @@ pub async fn simulate_bond_mixnode(
|
||||
.await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_pledge_more(
|
||||
additional_pledge: DecCoin,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
simulate_mixnet_operation(ExecuteMsg::PledgeMore {}, Some(additional_pledge), &state).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_unbond_mixnode(
|
||||
state: tauri::State<'_, WalletState>,
|
||||
|
||||
@@ -91,6 +91,19 @@ pub async fn simulate_vesting_bond_mixnode(
|
||||
.await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_vesting_pledge_more(
|
||||
additional_pledge: DecCoin,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let amount = guard
|
||||
.attempt_convert_to_base_coin(additional_pledge)?
|
||||
.into();
|
||||
|
||||
simulate_vesting_operation(ExecuteMsg::PledgeMore { amount }, None, &state).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_vesting_unbond_mixnode(
|
||||
state: tauri::State<'_, WalletState>,
|
||||
|
||||
@@ -93,6 +93,33 @@ pub async fn vesting_bond_mixnode(
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_pledge_more(
|
||||
fee: Option<Fee>,
|
||||
additional_pledge: DecCoin,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let additional_pledge_base = guard.attempt_convert_to_base_coin(additional_pledge.clone())?;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
log::info!(
|
||||
">>> Pledge more with locked tokens, additional_pledge_display = {}, additional_pledge_base = {}, fee = {:?}",
|
||||
additional_pledge,
|
||||
additional_pledge_base,
|
||||
fee,
|
||||
);
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.vesting_pledge_more(additional_pledge_base, fee)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_unbond_mixnode(
|
||||
fee: Option<Fee>,
|
||||
|
||||
Reference in New Issue
Block a user