1use cosmwasm_std::{coin, Addr, Coin, DepsMut, Env, MessageInfo, Response, Storage};
5
6use mixnet_contract_common::error::MixnetContractError;
7use mixnet_contract_common::events::{
8 new_mixnode_bonding_event, new_mixnode_config_update_event,
9 new_mixnode_pending_cost_params_update_event, new_pending_mixnode_unbonding_event,
10 new_pending_pledge_decrease_event, new_pending_pledge_increase_event,
11};
12use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
13use mixnet_contract_common::pending_events::{PendingEpochEventKind, PendingIntervalEventKind};
14use mixnet_contract_common::{Layer, MixId, MixNode};
15use nym_contracts_common::signing::MessageSignature;
16
17use crate::interval::storage as interval_storage;
18use crate::interval::storage::push_new_interval_event;
19use crate::mixnet_contract_settings::storage as mixnet_params_storage;
20use crate::mixnet_contract_settings::storage::rewarding_denom;
21use crate::mixnodes::helpers::{
22 get_mixnode_details_by_owner, must_get_mixnode_bond_by_owner, save_new_mixnode,
23};
24use crate::mixnodes::signature_helpers::verify_mixnode_bonding_signature;
25use crate::signing::storage as signing_storage;
26use crate::support::helpers::{
27 ensure_bonded, ensure_epoch_in_progress_state, ensure_is_authorized, ensure_no_existing_bond,
28 ensure_no_pending_pledge_changes, ensure_proxy_match, ensure_sent_by_vesting_contract,
29 validate_pledge,
30};
31
32use super::storage;
33
34pub(crate) fn update_mixnode_layer(
35 mix_id: MixId,
36 layer: Layer,
37 storage: &mut dyn Storage,
38) -> Result<(), MixnetContractError> {
39 let bond = if let Some(bond_information) = storage::mixnode_bonds().may_load(storage, mix_id)? {
40 bond_information
41 } else {
42 return Err(MixnetContractError::MixNodeBondNotFound { mix_id });
43 };
44 let mut updated_bond = bond.clone();
45 updated_bond.layer = layer;
46
47 storage::mixnode_bonds().replace(storage, bond.mix_id, Some(&updated_bond), Some(&bond))?;
48 Ok(())
49}
50
51pub fn assign_mixnode_layer(
52 deps: DepsMut<'_>,
53 info: MessageInfo,
54 mix_id: MixId,
55 layer: Layer,
56) -> Result<Response, MixnetContractError> {
57 ensure_is_authorized(&info.sender, deps.storage)?;
58
59 update_mixnode_layer(mix_id, layer, deps.storage)?;
60
61 Ok(Response::default())
62}
63
64pub fn try_add_mixnode(
65 deps: DepsMut<'_>,
66 env: Env,
67 info: MessageInfo,
68 mix_node: MixNode,
69 cost_params: MixNodeCostParams,
70 owner_signature: MessageSignature,
71) -> Result<Response, MixnetContractError> {
72 _try_add_mixnode(
73 deps,
74 env,
75 mix_node,
76 cost_params,
77 info.funds,
78 info.sender,
79 owner_signature,
80 None,
81 )
82}
83
84pub fn try_add_mixnode_on_behalf(
85 deps: DepsMut<'_>,
86 env: Env,
87 info: MessageInfo,
88 mix_node: MixNode,
89 cost_params: MixNodeCostParams,
90 owner: String,
91 owner_signature: MessageSignature,
92) -> Result<Response, MixnetContractError> {
93 ensure_sent_by_vesting_contract(&info, deps.storage)?;
94
95 let proxy = info.sender;
96 let owner = deps.api.addr_validate(&owner)?;
97 _try_add_mixnode(
98 deps,
99 env,
100 mix_node,
101 cost_params,
102 info.funds,
103 owner,
104 owner_signature,
105 Some(proxy),
106 )
107}
108
109#[allow(clippy::too_many_arguments)]
114fn _try_add_mixnode(
115 deps: DepsMut<'_>,
116 env: Env,
117 mixnode: MixNode,
118 cost_params: MixNodeCostParams,
119 pledge: Vec<Coin>,
120 owner: Addr,
121 owner_signature: MessageSignature,
122 proxy: Option<Addr>,
123) -> Result<Response, MixnetContractError> {
124 let minimum_pledge = mixnet_params_storage::minimum_mixnode_pledge(deps.storage)?;
126 let pledge = validate_pledge(pledge, minimum_pledge)?;
127
128 ensure_no_existing_bond(&owner, deps.storage)?;
132
133 verify_mixnode_bonding_signature(
139 deps.as_ref(),
140 owner.clone(),
141 proxy.clone(),
142 pledge.clone(),
143 mixnode.clone(),
144 cost_params.clone(),
145 owner_signature,
146 )?;
147
148 signing_storage::increment_signing_nonce(deps.storage, owner.clone())?;
150
151 let node_identity = mixnode.identity_key.clone();
152 let (node_id, layer) = save_new_mixnode(
153 deps.storage,
154 env,
155 mixnode,
156 cost_params,
157 owner.clone(),
158 proxy.clone(),
159 pledge.clone(),
160 )?;
161
162 Ok(Response::new().add_event(new_mixnode_bonding_event(
163 &owner,
164 &proxy,
165 &pledge,
166 &node_identity,
167 node_id,
168 layer,
169 )))
170}
171
172pub fn try_increase_pledge(
173 deps: DepsMut<'_>,
174 env: Env,
175 info: MessageInfo,
176) -> Result<Response, MixnetContractError> {
177 _try_increase_pledge(deps, env, info.funds, info.sender, None)
178}
179
180pub fn try_increase_pledge_on_behalf(
181 deps: DepsMut<'_>,
182 env: Env,
183 info: MessageInfo,
184 owner: String,
185) -> Result<Response, MixnetContractError> {
186 ensure_sent_by_vesting_contract(&info, deps.storage)?;
187
188 let proxy = info.sender;
189 let owner = deps.api.addr_validate(&owner)?;
190 _try_increase_pledge(deps, env, info.funds, owner, Some(proxy))
191}
192
193pub fn _try_increase_pledge(
194 deps: DepsMut<'_>,
195 env: Env,
196 increase: Vec<Coin>,
197 owner: Addr,
198 proxy: Option<Addr>,
199) -> Result<Response, MixnetContractError> {
200 let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())?
201 .ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })?;
202 let mut pending_changes = mix_details.pending_changes;
203 let mix_id = mix_details.mix_id();
204
205 ensure_epoch_in_progress_state(deps.storage)?;
207
208 ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?;
209 ensure_bonded(&mix_details.bond_information)?;
210 ensure_no_pending_pledge_changes(&pending_changes)?;
211
212 let rewarding_denom = rewarding_denom(deps.storage)?;
213 let pledge_increase = validate_pledge(increase, coin(1, rewarding_denom))?;
214
215 let cosmos_event = new_pending_pledge_increase_event(mix_id, &pledge_increase);
216
217 let epoch_event = PendingEpochEventKind::PledgeMore {
219 mix_id,
220 amount: pledge_increase,
221 };
222 let epoch_event_id = interval_storage::push_new_epoch_event(deps.storage, &env, epoch_event)?;
223 pending_changes.pledge_change = Some(epoch_event_id);
224 storage::PENDING_MIXNODE_CHANGES.save(deps.storage, mix_id, &pending_changes)?;
225
226 Ok(Response::new().add_event(cosmos_event))
227}
228
229pub fn try_decrease_pledge(
230 deps: DepsMut<'_>,
231 env: Env,
232 info: MessageInfo,
233 decrease_by: Coin,
234) -> Result<Response, MixnetContractError> {
235 _try_decrease_pledge(deps, env, decrease_by, info.sender, None)
236}
237
238pub fn try_decrease_pledge_on_behalf(
239 deps: DepsMut<'_>,
240 env: Env,
241 info: MessageInfo,
242 decrease_by: Coin,
243 owner: String,
244) -> Result<Response, MixnetContractError> {
245 ensure_sent_by_vesting_contract(&info, deps.storage)?;
246
247 let proxy = info.sender;
248 let owner = deps.api.addr_validate(&owner)?;
249 _try_decrease_pledge(deps, env, decrease_by, owner, Some(proxy))
250}
251
252pub fn _try_decrease_pledge(
253 deps: DepsMut<'_>,
254 env: Env,
255 decrease_by: Coin,
256 owner: Addr,
257 proxy: Option<Addr>,
258) -> Result<Response, MixnetContractError> {
259 let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())?
260 .ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })?;
261 let mut pending_changes = mix_details.pending_changes;
262 let mix_id = mix_details.mix_id();
263
264 ensure_epoch_in_progress_state(deps.storage)?;
266
267 ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?;
268 ensure_bonded(&mix_details.bond_information)?;
269 ensure_no_pending_pledge_changes(&pending_changes)?;
270
271 let minimum_pledge = mixnet_params_storage::minimum_mixnode_pledge(deps.storage)?;
272
273 if decrease_by.denom != minimum_pledge.denom {
275 return Err(MixnetContractError::WrongDenom {
276 received: decrease_by.denom,
277 expected: minimum_pledge.denom,
278 });
279 }
280
281 if decrease_by.amount.is_zero() {
284 return Err(MixnetContractError::ZeroCoinAmount);
285 }
286
287 let new_pledge_amount = mix_details
289 .original_pledge()
290 .amount
291 .saturating_sub(decrease_by.amount);
292 if new_pledge_amount < minimum_pledge.amount {
293 return Err(MixnetContractError::InvalidPledgeReduction {
294 current: mix_details.original_pledge().amount,
295 decrease_by: decrease_by.amount,
296 minimum: minimum_pledge.amount,
297 denom: minimum_pledge.denom,
298 });
299 }
300
301 let cosmos_event = new_pending_pledge_decrease_event(mix_id, &decrease_by);
302
303 let epoch_event = PendingEpochEventKind::DecreasePledge {
305 mix_id,
306 decrease_by,
307 };
308 let epoch_event_id = interval_storage::push_new_epoch_event(deps.storage, &env, epoch_event)?;
309 pending_changes.pledge_change = Some(epoch_event_id);
310 storage::PENDING_MIXNODE_CHANGES.save(deps.storage, mix_id, &pending_changes)?;
311
312 Ok(Response::new().add_event(cosmos_event))
313}
314
315pub fn try_remove_mixnode_on_behalf(
316 deps: DepsMut<'_>,
317 env: Env,
318 info: MessageInfo,
319 owner: String,
320) -> Result<Response, MixnetContractError> {
321 ensure_sent_by_vesting_contract(&info, deps.storage)?;
322
323 let proxy = info.sender;
324 let owner = deps.api.addr_validate(&owner)?;
325 _try_remove_mixnode(deps, env, owner, Some(proxy))
326}
327
328pub fn try_remove_mixnode(
329 deps: DepsMut<'_>,
330 env: Env,
331 info: MessageInfo,
332) -> Result<Response, MixnetContractError> {
333 _try_remove_mixnode(deps, env, info.sender, None)
334}
335
336pub(crate) fn _try_remove_mixnode(
337 deps: DepsMut<'_>,
338 env: Env,
339 owner: Addr,
340 proxy: Option<Addr>,
341) -> Result<Response, MixnetContractError> {
342 let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?;
343 let pending_changes = storage::PENDING_MIXNODE_CHANGES
344 .may_load(deps.storage, existing_bond.mix_id)?
345 .unwrap_or_default();
346
347 ensure_epoch_in_progress_state(deps.storage)?;
349
350 ensure_proxy_match(&proxy, &existing_bond.proxy)?;
352 ensure_bonded(&existing_bond)?;
353
354 ensure_no_pending_pledge_changes(&pending_changes)?;
356
357 let mut updated_bond = existing_bond.clone();
359 updated_bond.is_unbonding = true;
360 storage::mixnode_bonds().replace(
361 deps.storage,
362 existing_bond.mix_id,
363 Some(&updated_bond),
364 Some(&existing_bond),
365 )?;
366
367 let epoch_event = PendingEpochEventKind::UnbondMixnode {
369 mix_id: existing_bond.mix_id,
370 };
371 interval_storage::push_new_epoch_event(deps.storage, &env, epoch_event)?;
372
373 Ok(
374 Response::new().add_event(new_pending_mixnode_unbonding_event(
375 &existing_bond.owner,
376 &existing_bond.proxy,
377 existing_bond.identity(),
378 existing_bond.mix_id,
379 )),
380 )
381}
382
383pub(crate) fn try_update_mixnode_config(
384 deps: DepsMut<'_>,
385 info: MessageInfo,
386 new_config: MixNodeConfigUpdate,
387) -> Result<Response, MixnetContractError> {
388 let owner = info.sender;
389 _try_update_mixnode_config(deps, new_config, owner, None)
390}
391
392pub(crate) fn try_update_mixnode_config_on_behalf(
393 deps: DepsMut<'_>,
394 info: MessageInfo,
395 new_config: MixNodeConfigUpdate,
396 owner: String,
397) -> Result<Response, MixnetContractError> {
398 ensure_sent_by_vesting_contract(&info, deps.storage)?;
399
400 let owner = deps.api.addr_validate(&owner)?;
401 let proxy = info.sender;
402 _try_update_mixnode_config(deps, new_config, owner, Some(proxy))
403}
404
405pub(crate) fn _try_update_mixnode_config(
406 deps: DepsMut<'_>,
407 new_config: MixNodeConfigUpdate,
408 owner: Addr,
409 proxy: Option<Addr>,
410) -> Result<Response, MixnetContractError> {
411 let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?;
412
413 ensure_bonded(&existing_bond)?;
414 ensure_proxy_match(&proxy, &existing_bond.proxy)?;
415
416 let cfg_update_event =
417 new_mixnode_config_update_event(existing_bond.mix_id, &owner, &proxy, &new_config);
418
419 let mut updated_bond = existing_bond.clone();
420 updated_bond.mix_node.host = new_config.host;
421 updated_bond.mix_node.mix_port = new_config.mix_port;
422 updated_bond.mix_node.verloc_port = new_config.verloc_port;
423 updated_bond.mix_node.http_api_port = new_config.http_api_port;
424 updated_bond.mix_node.version = new_config.version;
425
426 storage::mixnode_bonds().replace(
427 deps.storage,
428 existing_bond.mix_id,
429 Some(&updated_bond),
430 Some(&existing_bond),
431 )?;
432
433 Ok(Response::new().add_event(cfg_update_event))
434}
435
436pub(crate) fn try_update_mixnode_cost_params(
437 deps: DepsMut<'_>,
438 env: Env,
439 info: MessageInfo,
440 new_costs: MixNodeCostParams,
441) -> Result<Response, MixnetContractError> {
442 let owner = info.sender;
443 _try_update_mixnode_cost_params(deps, env, new_costs, owner, None)
444}
445
446pub(crate) fn try_update_mixnode_cost_params_on_behalf(
447 deps: DepsMut,
448 env: Env,
449 info: MessageInfo,
450 new_costs: MixNodeCostParams,
451 owner: String,
452) -> Result<Response, MixnetContractError> {
453 ensure_sent_by_vesting_contract(&info, deps.storage)?;
454
455 let owner = deps.api.addr_validate(&owner)?;
456 let proxy = info.sender;
457 _try_update_mixnode_cost_params(deps, env, new_costs, owner, Some(proxy))
458}
459
460pub(crate) fn _try_update_mixnode_cost_params(
461 deps: DepsMut,
462 env: Env,
463 new_costs: MixNodeCostParams,
464 owner: Addr,
465 proxy: Option<Addr>,
466) -> Result<Response, MixnetContractError> {
467 let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?;
469
470 ensure_epoch_in_progress_state(deps.storage)?;
472
473 ensure_proxy_match(&proxy, &existing_bond.proxy)?;
474 ensure_bonded(&existing_bond)?;
475
476 let cosmos_event = new_mixnode_pending_cost_params_update_event(
477 existing_bond.mix_id,
478 &owner,
479 &proxy,
480 &new_costs,
481 );
482
483 let interval_event = PendingIntervalEventKind::ChangeMixCostParams {
485 mix_id: existing_bond.mix_id,
486 new_costs,
487 };
488 push_new_interval_event(deps.storage, &env, interval_event)?;
489
490 Ok(Response::new().add_event(cosmos_event))
491}
492
493#[cfg(test)]
494pub mod tests {
495 use cosmwasm_std::testing::mock_info;
496 use cosmwasm_std::{Order, StdResult, Uint128};
497
498 use mixnet_contract_common::mixnode::PendingMixNodeChanges;
499 use mixnet_contract_common::{
500 EpochState, EpochStatus, ExecuteMsg, Layer, LayerDistribution, Percent,
501 };
502
503 use crate::contract::execute;
504 use crate::mixnet_contract_settings::storage::minimum_mixnode_pledge;
505 use crate::mixnodes::helpers::get_mixnode_details_by_id;
506 use crate::support::tests::fixtures::{good_mixnode_pledge, TEST_COIN_DENOM};
507 use crate::support::tests::test_helpers::TestSetup;
508 use crate::support::tests::{fixtures, test_helpers};
509
510 use super::*;
511
512 #[test]
513 fn mixnode_add() {
514 let mut test = TestSetup::new();
515 let env = test.env();
516
517 let sender = "alice";
518 let minimum_pledge = minimum_mixnode_pledge(test.deps().storage).unwrap();
519 let mut insufficient_pledge = minimum_pledge.clone();
520 insufficient_pledge.amount -= Uint128::new(1000);
521
522 let info = mock_info(sender, &[insufficient_pledge.clone()]);
524 let (mixnode, sig, _) =
525 test.mixnode_with_signature(sender, Some(vec![insufficient_pledge.clone()]));
526 let cost_params = fixtures::mix_node_cost_params_fixture();
527
528 let result = try_add_mixnode(
530 test.deps_mut(),
531 env.clone(),
532 info,
533 mixnode.clone(),
534 cost_params.clone(),
535 sig.clone(),
536 );
537 assert_eq!(
538 result,
539 Err(MixnetContractError::InsufficientPledge {
540 received: insufficient_pledge,
541 minimum: minimum_pledge.clone(),
542 })
543 );
544
545 let info = mock_info(sender, &[minimum_pledge]);
547
548 test.add_dummy_mixnode(sender, None);
550
551 let result = try_add_mixnode(
553 test.deps_mut(),
554 env.clone(),
555 info,
556 mixnode,
557 cost_params.clone(),
558 sig,
559 );
560 assert_eq!(Err(MixnetContractError::AlreadyOwnsMixnode), result);
561
562 let sender2 = "gateway-owner";
564
565 test.add_dummy_gateway(sender2, None);
566
567 let info = mock_info(sender2, &tests::fixtures::good_mixnode_pledge());
568 let (mixnode, sig, _) = test.mixnode_with_signature(sender2, None);
569
570 let result = try_add_mixnode(
571 test.deps_mut(),
572 env.clone(),
573 info.clone(),
574 mixnode.clone(),
575 cost_params.clone(),
576 sig.clone(),
577 );
578 assert_eq!(Err(MixnetContractError::AlreadyOwnsGateway), result);
579
580 let msg = ExecuteMsg::UnbondGateway {};
582 execute(test.deps_mut(), env.clone(), info.clone(), msg).unwrap();
583
584 let result = try_add_mixnode(test.deps_mut(), env, info, mixnode, cost_params, sig);
585 assert!(result.is_ok());
586
587 let bond =
589 must_get_mixnode_bond_by_owner(test.deps().storage, &Addr::unchecked(sender2)).unwrap();
590 assert_eq!(2, bond.mix_id);
591
592 assert_eq!(Layer::Two, bond.layer);
594
595 let expected = LayerDistribution {
597 layer1: 1,
598 layer2: 1,
599 layer3: 0,
600 };
601 assert_eq!(expected, storage::LAYERS.load(test.deps().storage).unwrap())
602 }
603
604 #[test]
605 fn adding_mixnode_with_invalid_signatures() {
606 let mut test = TestSetup::new();
607 let env = test.env();
608
609 let sender = "alice";
610 let pledge = good_mixnode_pledge();
611 let info = mock_info(sender, pledge.as_ref());
612
613 let (mixnode, signature, _) = test.mixnode_with_signature(sender, Some(pledge.clone()));
614 let cost_params = fixtures::mix_node_cost_params_fixture();
616
617 let mut modified_mixnode = mixnode.clone();
619 modified_mixnode.mix_port += 1;
620 let res = try_add_mixnode(
621 test.deps_mut(),
622 env.clone(),
623 info,
624 modified_mixnode,
625 cost_params.clone(),
626 signature.clone(),
627 );
628 assert_eq!(res, Err(MixnetContractError::InvalidEd25519Signature));
629
630 let mut different_pledge = pledge.clone();
632 different_pledge[0].amount += Uint128::new(12345);
633
634 let info = mock_info(sender, different_pledge.as_ref());
635 let res = try_add_mixnode(
636 test.deps_mut(),
637 env.clone(),
638 info,
639 mixnode.clone(),
640 cost_params.clone(),
641 signature.clone(),
642 );
643 assert_eq!(res, Err(MixnetContractError::InvalidEd25519Signature));
644
645 let other_sender = mock_info("another-sender", pledge.as_ref());
646 let res = try_add_mixnode(
647 test.deps_mut(),
648 env.clone(),
649 other_sender,
650 mixnode.clone(),
651 cost_params.clone(),
652 signature.clone(),
653 );
654 assert_eq!(res, Err(MixnetContractError::InvalidEd25519Signature));
655
656 let info = mock_info(sender, pledge.as_ref());
658 let current_nonce =
659 signing_storage::get_signing_nonce(test.deps().storage, Addr::unchecked(sender))
660 .unwrap();
661 assert_eq!(0, current_nonce);
662 let res = try_add_mixnode(
663 test.deps_mut(),
664 env.clone(),
665 info.clone(),
666 mixnode.clone(),
667 cost_params.clone(),
668 signature.clone(),
669 );
670 assert!(res.is_ok());
671 let updated_nonce =
672 signing_storage::get_signing_nonce(test.deps().storage, Addr::unchecked(sender))
673 .unwrap();
674 assert_eq!(1, updated_nonce);
675
676 test.immediately_unbond_mixnode(1);
677 let res = try_add_mixnode(test.deps_mut(), env, info, mixnode, cost_params, signature);
678 assert_eq!(res, Err(MixnetContractError::InvalidEd25519Signature));
679 }
680
681 #[test]
682 fn mixnode_add_with_illegal_proxy() {
683 let mut test = TestSetup::new();
684 let env = test.env();
685
686 let illegal_proxy = Addr::unchecked("not-vesting-contract");
687 let vesting_contract = test.vesting_contract();
688
689 let owner = "alice";
690 let (mixnode, sig, _) = test.mixnode_with_signature(owner, None);
691 let cost_params = fixtures::mix_node_cost_params_fixture();
692
693 let res = try_add_mixnode_on_behalf(
694 test.deps_mut(),
695 env,
696 mock_info(illegal_proxy.as_ref(), &good_mixnode_pledge()),
697 mixnode,
698 cost_params,
699 owner.to_string(),
700 sig,
701 )
702 .unwrap_err();
703
704 assert_eq!(
705 res,
706 MixnetContractError::SenderIsNotVestingContract {
707 received: illegal_proxy,
708 vesting_contract,
709 }
710 )
711 }
712
713 #[test]
714 fn removing_mixnode_cant_be_performed_if_epoch_transition_is_in_progress() {
715 let bad_states = vec![
716 EpochState::Rewarding {
717 last_rewarded: 0,
718 final_node_id: 0,
719 },
720 EpochState::ReconcilingEvents,
721 EpochState::AdvancingEpoch,
722 ];
723
724 for bad_state in bad_states {
725 let mut test = TestSetup::new();
726 let env = test.env();
727 let owner = "alice";
728 let info = mock_info(owner, &[]);
729
730 test.add_dummy_mixnode(owner, None);
731
732 let mut status = EpochStatus::new(test.rewarding_validator().sender);
733 status.state = bad_state;
734 interval_storage::save_current_epoch_status(test.deps_mut().storage, &status).unwrap();
735
736 let res = try_remove_mixnode(test.deps_mut(), env.clone(), info);
737 assert!(matches!(
738 res,
739 Err(MixnetContractError::EpochAdvancementInProgress { .. })
740 ));
741 }
742 }
743
744 #[test]
745 fn mixnode_remove() {
746 let mut test = TestSetup::new();
747 let env = test.env();
748
749 let owner = "alice";
750 let info = mock_info(owner, &[]);
751
752 let res = try_remove_mixnode(test.deps_mut(), env.clone(), info.clone());
754 assert_eq!(
755 res,
756 Err(MixnetContractError::NoAssociatedMixNodeBond {
757 owner: Addr::unchecked(owner)
758 })
759 );
760
761 let mix_id = test.add_dummy_mixnode(owner, None);
762 let vesting_contract = test.vesting_contract();
763
764 let res = try_remove_mixnode_on_behalf(
766 test.deps_mut(),
767 env.clone(),
768 mock_info(vesting_contract.as_ref(), &[]),
769 owner.to_string(),
770 );
771
772 assert_eq!(
773 res,
774 Err(MixnetContractError::ProxyMismatch {
775 existing: "None".to_string(),
776 incoming: vesting_contract.into_string(),
777 })
778 );
779
780 let res = try_remove_mixnode(test.deps_mut(), env.clone(), info.clone());
782 assert!(res.is_ok());
783 let mut pending_events = interval_storage::PENDING_EPOCH_EVENTS
784 .range(test.deps().storage, None, None, Order::Ascending)
785 .collect::<StdResult<Vec<_>>>()
786 .unwrap();
787 assert_eq!(pending_events.len(), 1);
788 let event = pending_events.pop().unwrap();
789 assert_eq!(1, event.0);
790 assert_eq!(
791 PendingEpochEventKind::UnbondMixnode { mix_id },
792 event.1.kind
793 );
794
795 let res = try_remove_mixnode(test.deps_mut(), env, info);
797 assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id }))
798 }
799
800 #[test]
801 fn mixnode_remove_with_illegal_proxy() {
802 let mut test = TestSetup::new();
803 let env = test.env();
804
805 let illegal_proxy = Addr::unchecked("not-vesting-contract");
806 let vesting_contract = test.vesting_contract();
807
808 let owner = "alice";
809
810 test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone());
811
812 let res = try_remove_mixnode_on_behalf(
813 test.deps_mut(),
814 env,
815 mock_info(illegal_proxy.as_ref(), &[]),
816 owner.to_string(),
817 )
818 .unwrap_err();
819
820 assert_eq!(
821 res,
822 MixnetContractError::SenderIsNotVestingContract {
823 received: illegal_proxy,
824 vesting_contract,
825 }
826 )
827 }
828
829 #[test]
830 fn mixnode_remove_is_not_allowed_if_there_are_pending_pledge_changes() {
831 let mut test = TestSetup::new();
832 let env = test.env();
833
834 let owner = "mix-owner1";
836 test.add_dummy_mixnode(owner, None);
837 let sender = mock_info(owner, &[test.coin(1000)]);
838 try_increase_pledge(test.deps_mut(), env.clone(), sender.clone()).unwrap();
839
840 let res = try_remove_mixnode(test.deps_mut(), env.clone(), sender);
841 assert_eq!(
842 res,
843 Err(MixnetContractError::PendingPledgeChange {
844 pending_event_id: 1
845 })
846 );
847
848 let owner = "mix-owner2";
850 test.add_dummy_mixnode(owner, Some(Uint128::new(10000000000)));
851 let sender = mock_info(owner, &[]);
852 let amount = test.coin(1000);
853 try_decrease_pledge(test.deps_mut(), env.clone(), sender, amount).unwrap();
854
855 let sender = mock_info(owner, &[test.coin(1000)]);
856 let res = try_remove_mixnode(test.deps_mut(), env.clone(), sender);
857 assert_eq!(
858 res,
859 Err(MixnetContractError::PendingPledgeChange {
860 pending_event_id: 2
861 })
862 );
863
864 let owner = "mix-owner3";
866 let mix_id = test.add_dummy_mixnode(owner, None);
867 let pending_change = PendingMixNodeChanges {
868 pledge_change: Some(1234),
869 };
870 storage::PENDING_MIXNODE_CHANGES
871 .save(test.deps_mut().storage, mix_id, &pending_change)
872 .unwrap();
873
874 let sender = mock_info(owner, &[test.coin(1000)]);
875 let res = try_remove_mixnode(test.deps_mut(), env, sender);
876 assert_eq!(
877 res,
878 Err(MixnetContractError::PendingPledgeChange {
879 pending_event_id: 1234
880 })
881 );
882 }
883
884 #[test]
885 fn updating_mixnode_config() {
886 let mut test = TestSetup::new();
887 let env = test.env();
888
889 let owner = "alice";
890 let info = mock_info(owner, &[]);
891 let update = MixNodeConfigUpdate {
892 host: "1.1.1.1:1234".to_string(),
893 mix_port: 1234,
894 verloc_port: 1235,
895 http_api_port: 1236,
896 version: "v1.2.3".to_string(),
897 };
898
899 let res = try_update_mixnode_config(test.deps_mut(), info.clone(), update.clone());
901 assert_eq!(
902 res,
903 Err(MixnetContractError::NoAssociatedMixNodeBond {
904 owner: Addr::unchecked(owner)
905 })
906 );
907
908 let mix_id = test.add_dummy_mixnode(owner, None);
909 let vesting_contract = test.vesting_contract();
910
911 let res = try_update_mixnode_config_on_behalf(
913 test.deps_mut(),
914 mock_info(vesting_contract.as_ref(), &[]),
915 update.clone(),
916 owner.to_string(),
917 );
918 assert_eq!(
919 res,
920 Err(MixnetContractError::ProxyMismatch {
921 existing: "None".to_string(),
922 incoming: vesting_contract.into_string(),
923 })
924 );
925 let res = try_update_mixnode_config(test.deps_mut(), info.clone(), update.clone());
927 assert!(res.is_ok());
928
929 let mix =
931 must_get_mixnode_bond_by_owner(test.deps().storage, &Addr::unchecked(owner)).unwrap();
932 assert_eq!(mix.mix_node.host, update.host);
933 assert_eq!(mix.mix_node.mix_port, update.mix_port);
934 assert_eq!(mix.mix_node.verloc_port, update.verloc_port);
935 assert_eq!(mix.mix_node.http_api_port, update.http_api_port);
936 assert_eq!(mix.mix_node.version, update.version);
937
938 try_remove_mixnode(test.deps_mut(), env, info.clone()).unwrap();
940 let res = try_update_mixnode_config(test.deps_mut(), info, update);
941 assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id }))
942 }
943
944 #[test]
945 fn updating_mixnode_config_with_illegal_proxy() {
946 let mut test = TestSetup::new();
947
948 let illegal_proxy = Addr::unchecked("not-vesting-contract");
949 let vesting_contract = test.vesting_contract();
950
951 let owner = "alice";
952
953 test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone());
954 let update = MixNodeConfigUpdate {
955 host: "1.1.1.1:1234".to_string(),
956 mix_port: 1234,
957 verloc_port: 1235,
958 http_api_port: 1236,
959 version: "v1.2.3".to_string(),
960 };
961
962 let res = try_update_mixnode_config_on_behalf(
963 test.deps_mut(),
964 mock_info(illegal_proxy.as_ref(), &[]),
965 update,
966 owner.to_string(),
967 )
968 .unwrap_err();
969
970 assert_eq!(
971 res,
972 MixnetContractError::SenderIsNotVestingContract {
973 received: illegal_proxy,
974 vesting_contract,
975 }
976 )
977 }
978
979 #[test]
980 fn mixnode_cost_params_cant_be_updated_when_epoch_transition_is_in_progress() {
981 let bad_states = vec![
982 EpochState::Rewarding {
983 last_rewarded: 0,
984 final_node_id: 0,
985 },
986 EpochState::ReconcilingEvents,
987 EpochState::AdvancingEpoch,
988 ];
989
990 let update = MixNodeCostParams {
991 profit_margin_percent: Percent::from_percentage_value(42).unwrap(),
992 interval_operating_cost: Coin::new(12345678, TEST_COIN_DENOM),
993 };
994
995 for bad_state in bad_states {
996 let mut test = TestSetup::new();
997 let env = test.env();
998 let owner = "alice";
999 let info = mock_info(owner, &[]);
1000
1001 test.add_dummy_mixnode(owner, None);
1002
1003 let mut status = EpochStatus::new(test.rewarding_validator().sender);
1004 status.state = bad_state;
1005 interval_storage::save_current_epoch_status(test.deps_mut().storage, &status).unwrap();
1006
1007 let res =
1008 try_update_mixnode_cost_params(test.deps_mut(), env.clone(), info, update.clone());
1009 assert!(matches!(
1010 res,
1011 Err(MixnetContractError::EpochAdvancementInProgress { .. })
1012 ));
1013 }
1014 }
1015
1016 #[test]
1017 fn updating_mixnode_cost_params() {
1018 let mut test = TestSetup::new();
1019 let env = test.env();
1020
1021 let owner = "alice";
1022 let info = mock_info(owner, &[]);
1023 let update = MixNodeCostParams {
1024 profit_margin_percent: Percent::from_percentage_value(42).unwrap(),
1025 interval_operating_cost: Coin::new(12345678, TEST_COIN_DENOM),
1026 };
1027
1028 let res = try_update_mixnode_cost_params(
1030 test.deps_mut(),
1031 env.clone(),
1032 info.clone(),
1033 update.clone(),
1034 );
1035 assert_eq!(
1036 res,
1037 Err(MixnetContractError::NoAssociatedMixNodeBond {
1038 owner: Addr::unchecked(owner)
1039 })
1040 );
1041
1042 let mix_id = test.add_dummy_mixnode(owner, None);
1043 let vesting_contract = test.vesting_contract();
1044
1045 let res = try_update_mixnode_cost_params_on_behalf(
1047 test.deps_mut(),
1048 env.clone(),
1049 mock_info(vesting_contract.as_ref(), &[]),
1050 update.clone(),
1051 owner.to_string(),
1052 );
1053 assert_eq!(
1054 res,
1055 Err(MixnetContractError::ProxyMismatch {
1056 existing: "None".to_string(),
1057 incoming: vesting_contract.into_string(),
1058 })
1059 );
1060 let res = try_update_mixnode_cost_params(
1062 test.deps_mut(),
1063 env.clone(),
1064 info.clone(),
1065 update.clone(),
1066 );
1067 assert!(res.is_ok());
1068
1069 let mut pending_events = interval_storage::PENDING_INTERVAL_EVENTS
1071 .range(test.deps().storage, None, None, Order::Ascending)
1072 .collect::<StdResult<Vec<_>>>()
1073 .unwrap();
1074 assert_eq!(pending_events.len(), 1);
1075 let event = pending_events.pop().unwrap();
1076 assert_eq!(1, event.0);
1077 assert_eq!(
1078 PendingIntervalEventKind::ChangeMixCostParams {
1079 mix_id,
1080 new_costs: update.clone(),
1081 },
1082 event.1.kind
1083 );
1084
1085 test_helpers::execute_all_pending_events(test.deps_mut(), env.clone());
1087
1088 let mix = get_mixnode_details_by_id(test.deps().storage, mix_id)
1090 .unwrap()
1091 .unwrap();
1092 assert_eq!(mix.rewarding_details.cost_params, update);
1093
1094 try_remove_mixnode(test.deps_mut(), env.clone(), info.clone()).unwrap();
1096 let res = try_update_mixnode_cost_params(test.deps_mut(), env, info, update);
1097 assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id }))
1098 }
1099
1100 #[test]
1101 fn updating_mixnode_cost_params_with_illegal_proxy() {
1102 let mut test = TestSetup::new();
1103 let env = test.env();
1104
1105 let illegal_proxy = Addr::unchecked("not-vesting-contract");
1106 let vesting_contract = test.vesting_contract();
1107
1108 let owner = "alice";
1109
1110 test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone());
1111 let update = MixNodeCostParams {
1112 profit_margin_percent: Percent::from_percentage_value(42).unwrap(),
1113 interval_operating_cost: Coin::new(12345678, TEST_COIN_DENOM),
1114 };
1115
1116 let res = try_update_mixnode_cost_params_on_behalf(
1117 test.deps_mut(),
1118 env,
1119 mock_info(illegal_proxy.as_ref(), &[]),
1120 update,
1121 owner.to_string(),
1122 )
1123 .unwrap_err();
1124
1125 assert_eq!(
1126 res,
1127 MixnetContractError::SenderIsNotVestingContract {
1128 received: illegal_proxy,
1129 vesting_contract,
1130 }
1131 )
1132 }
1133
1134 #[test]
1135 fn adding_mixnode_with_duplicate_sphinx_key_errors_out() {
1136 let mut test = TestSetup::new();
1137 let env = test.env();
1138
1139 let keypair1 = nym_crypto::asymmetric::identity::KeyPair::new(&mut test.rng);
1140 let keypair2 = nym_crypto::asymmetric::identity::KeyPair::new(&mut test.rng);
1141
1142 let cost_params = fixtures::mix_node_cost_params_fixture();
1143 let mixnode1 = MixNode {
1144 host: "1.2.3.4".to_string(),
1145 mix_port: 1234,
1146 verloc_port: 1234,
1147 http_api_port: 1234,
1148 sphinx_key: nym_crypto::asymmetric::encryption::KeyPair::new(&mut test.rng)
1149 .public_key()
1150 .to_base58_string(),
1151 identity_key: keypair1.public_key().to_base58_string(),
1152 version: "v0.1.2.3".to_string(),
1153 };
1154
1155 let mut mixnode2 = mixnode1.clone();
1157 mixnode2.sphinx_key = nym_crypto::asymmetric::encryption::KeyPair::new(&mut test.rng)
1158 .public_key()
1159 .to_base58_string();
1160
1161 let sig1 =
1162 test.mixnode_bonding_signature(keypair1.private_key(), "alice", mixnode1.clone(), None);
1163 let sig2 =
1164 test.mixnode_bonding_signature(keypair2.private_key(), "bob", mixnode2.clone(), None);
1165
1166 let info_alice = mock_info("alice", &tests::fixtures::good_mixnode_pledge());
1167 let info_bob = mock_info("bob", &tests::fixtures::good_mixnode_pledge());
1168
1169 assert!(try_add_mixnode(
1170 test.deps_mut(),
1171 env.clone(),
1172 info_alice,
1173 mixnode1,
1174 cost_params.clone(),
1175 sig1,
1176 )
1177 .is_ok());
1178
1179 assert!(
1181 try_add_mixnode(test.deps_mut(), env, info_bob, mixnode2, cost_params, sig2).is_err()
1182 );
1183 }
1184
1185 #[cfg(test)]
1186 mod increasing_mixnode_pledge {
1187 use mixnet_contract_common::mixnode::PendingMixNodeChanges;
1188 use mixnet_contract_common::{EpochState, EpochStatus};
1189
1190 use crate::mixnodes::helpers::tests::{
1191 setup_mix_combinations, OWNER_UNBONDED, OWNER_UNBONDED_LEFTOVER, OWNER_UNBONDING,
1192 };
1193 use crate::support::tests::test_helpers::TestSetup;
1194
1195 use super::*;
1196
1197 #[test]
1198 fn cant_be_performed_if_epoch_transition_is_in_progress() {
1199 let bad_states = vec![
1200 EpochState::Rewarding {
1201 last_rewarded: 0,
1202 final_node_id: 0,
1203 },
1204 EpochState::ReconcilingEvents,
1205 EpochState::AdvancingEpoch,
1206 ];
1207
1208 for bad_state in bad_states {
1209 let mut test = TestSetup::new();
1210 let env = test.env();
1211 let owner = "mix-owner";
1212
1213 test.add_dummy_mixnode(owner, None);
1214
1215 let mut status = EpochStatus::new(test.rewarding_validator().sender);
1216 status.state = bad_state;
1217 interval_storage::save_current_epoch_status(test.deps_mut().storage, &status)
1218 .unwrap();
1219
1220 let sender = mock_info(owner, &[test.coin(1000)]);
1221 let res = try_increase_pledge(test.deps_mut(), env, sender);
1222
1223 assert!(matches!(
1224 res,
1225 Err(MixnetContractError::EpochAdvancementInProgress { .. })
1226 ));
1227 }
1228 }
1229
1230 #[test]
1231 fn is_not_allowed_if_account_doesnt_own_mixnode() {
1232 let mut test = TestSetup::new();
1233 let env = test.env();
1234 let sender = mock_info("not-mix-owner", &[]);
1235
1236 let res = try_increase_pledge(test.deps_mut(), env, sender);
1237 assert_eq!(
1238 res,
1239 Err(MixnetContractError::NoAssociatedMixNodeBond {
1240 owner: Addr::unchecked("not-mix-owner")
1241 })
1242 )
1243 }
1244
1245 #[test]
1246 fn is_not_allowed_if_theres_proxy_mismatch() {
1247 let mut test = TestSetup::new();
1248 let env = test.env();
1249
1250 let owner_without_proxy = Addr::unchecked("no-proxy");
1251 let owner_with_proxy = Addr::unchecked("with-proxy");
1252 let proxy = Addr::unchecked("proxy");
1253 let wrong_proxy = Addr::unchecked("unrelated-proxy");
1254
1255 test.add_dummy_mixnode(owner_without_proxy.as_str(), None);
1256 test.add_dummy_mixnode_with_illegal_proxy(
1257 owner_with_proxy.as_str(),
1258 None,
1259 proxy.clone(),
1260 );
1261
1262 let res = _try_increase_pledge(
1263 test.deps_mut(),
1264 env.clone(),
1265 Vec::new(),
1266 owner_without_proxy.clone(),
1267 Some(proxy),
1268 );
1269 assert_eq!(
1270 res,
1271 Err(MixnetContractError::ProxyMismatch {
1272 existing: "None".to_string(),
1273 incoming: "proxy".to_string(),
1274 })
1275 );
1276
1277 let res = _try_increase_pledge(
1278 test.deps_mut(),
1279 env.clone(),
1280 Vec::new(),
1281 owner_with_proxy.clone(),
1282 None,
1283 );
1284 assert_eq!(
1285 res,
1286 Err(MixnetContractError::ProxyMismatch {
1287 existing: "proxy".to_string(),
1288 incoming: "None".to_string(),
1289 })
1290 );
1291
1292 let res = _try_increase_pledge(
1293 test.deps_mut(),
1294 env,
1295 Vec::new(),
1296 owner_with_proxy.clone(),
1297 Some(wrong_proxy),
1298 );
1299 assert_eq!(
1300 res,
1301 Err(MixnetContractError::ProxyMismatch {
1302 existing: "proxy".to_string(),
1303 incoming: "unrelated-proxy".to_string(),
1304 })
1305 )
1306 }
1307
1308 #[test]
1309 fn is_not_allowed_if_mixnode_has_unbonded_or_is_unbonding() {
1310 let mut test = TestSetup::new();
1311 let env = test.env();
1312
1313 let owner_unbonding = Addr::unchecked(OWNER_UNBONDING);
1316 let owner_unbonded = Addr::unchecked(OWNER_UNBONDED);
1317 let owner_unbonded_leftover = Addr::unchecked(OWNER_UNBONDED_LEFTOVER);
1318
1319 let ids = setup_mix_combinations(&mut test, None);
1320 let mix_id_unbonding = ids[1].mix_id;
1321
1322 let res = try_increase_pledge(
1323 test.deps_mut(),
1324 env.clone(),
1325 mock_info(owner_unbonding.as_str(), &[]),
1326 );
1327 assert_eq!(
1328 res,
1329 Err(MixnetContractError::MixnodeIsUnbonding {
1330 mix_id: mix_id_unbonding
1331 })
1332 );
1333
1334 let res = try_increase_pledge(
1337 test.deps_mut(),
1338 env.clone(),
1339 mock_info(owner_unbonded_leftover.as_str(), &[]),
1340 );
1341 assert_eq!(
1342 res,
1343 Err(MixnetContractError::NoAssociatedMixNodeBond {
1344 owner: owner_unbonded_leftover
1345 })
1346 );
1347
1348 let res = try_increase_pledge(
1349 test.deps_mut(),
1350 env,
1351 mock_info(owner_unbonded.as_str(), &[]),
1352 );
1353 assert_eq!(
1354 res,
1355 Err(MixnetContractError::NoAssociatedMixNodeBond {
1356 owner: owner_unbonded
1357 })
1358 )
1359 }
1360
1361 #[test]
1362 fn is_not_allowed_if_no_tokens_were_sent() {
1363 let mut test = TestSetup::new();
1364 let env = test.env();
1365 let owner = "mix-owner";
1366
1367 test.add_dummy_mixnode(owner, None);
1368
1369 let sender_empty = mock_info(owner, &[]);
1370 let res = try_increase_pledge(test.deps_mut(), env.clone(), sender_empty);
1371 assert_eq!(res, Err(MixnetContractError::NoBondFound));
1372
1373 let sender_zero = mock_info(owner, &[test.coin(0)]);
1374 let res = try_increase_pledge(test.deps_mut(), env, sender_zero);
1375 assert_eq!(
1376 res,
1377 Err(MixnetContractError::InsufficientPledge {
1378 received: test.coin(0),
1379 minimum: test.coin(1),
1380 })
1381 )
1382 }
1383
1384 #[test]
1385 fn is_not_allowed_if_there_are_pending_pledge_changes() {
1386 let mut test = TestSetup::new();
1387 let env = test.env();
1388
1389 let owner = "mix-owner1";
1391 test.add_dummy_mixnode(owner, None);
1392 let sender = mock_info(owner, &[test.coin(1000)]);
1393 try_increase_pledge(test.deps_mut(), env.clone(), sender.clone()).unwrap();
1394
1395 let res = try_increase_pledge(test.deps_mut(), env.clone(), sender);
1396 assert_eq!(
1397 res,
1398 Err(MixnetContractError::PendingPledgeChange {
1399 pending_event_id: 1
1400 })
1401 );
1402
1403 let owner = "mix-owner2";
1405 test.add_dummy_mixnode(owner, Some(Uint128::new(10000000000)));
1406 let sender = mock_info(owner, &[]);
1407 let amount = test.coin(1000);
1408 try_decrease_pledge(test.deps_mut(), env.clone(), sender, amount).unwrap();
1409
1410 let sender = mock_info(owner, &[test.coin(1000)]);
1411 let res = try_increase_pledge(test.deps_mut(), env.clone(), sender);
1412 assert_eq!(
1413 res,
1414 Err(MixnetContractError::PendingPledgeChange {
1415 pending_event_id: 2
1416 })
1417 );
1418
1419 let owner = "mix-owner3";
1421 let mix_id = test.add_dummy_mixnode(owner, None);
1422 let pending_change = PendingMixNodeChanges {
1423 pledge_change: Some(1234),
1424 };
1425 storage::PENDING_MIXNODE_CHANGES
1426 .save(test.deps_mut().storage, mix_id, &pending_change)
1427 .unwrap();
1428
1429 let sender = mock_info(owner, &[test.coin(1000)]);
1430 let res = try_increase_pledge(test.deps_mut(), env, sender);
1431 assert_eq!(
1432 res,
1433 Err(MixnetContractError::PendingPledgeChange {
1434 pending_event_id: 1234
1435 })
1436 );
1437 }
1438
1439 #[test]
1440 fn with_valid_information_creates_pending_event() {
1441 let mut test = TestSetup::new();
1442 let env = test.env();
1443 let owner = "mix-owner";
1444 let mix_id = test.add_dummy_mixnode(owner, None);
1445
1446 let events = test.pending_epoch_events();
1447 assert!(events.is_empty());
1448
1449 let sender = mock_info(owner, &[test.coin(1000)]);
1450 try_increase_pledge(test.deps_mut(), env, sender).unwrap();
1451
1452 let events = test.pending_epoch_events();
1453
1454 assert_eq!(
1455 events[0].kind,
1456 PendingEpochEventKind::PledgeMore {
1457 mix_id,
1458 amount: test.coin(1000),
1459 }
1460 );
1461 }
1462
1463 #[test]
1464 fn fails_for_illegal_proxy() {
1465 let mut test = TestSetup::new();
1466 let env = test.env();
1467
1468 let illegal_proxy = Addr::unchecked("not-vesting-contract");
1469 let vesting_contract = test.vesting_contract();
1470
1471 let owner = "alice";
1472
1473 test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone());
1474
1475 let res = try_increase_pledge_on_behalf(
1476 test.deps_mut(),
1477 env,
1478 mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
1479 owner.to_string(),
1480 )
1481 .unwrap_err();
1482
1483 assert_eq!(
1484 res,
1485 MixnetContractError::SenderIsNotVestingContract {
1486 received: illegal_proxy,
1487 vesting_contract,
1488 }
1489 )
1490 }
1491 }
1492
1493 #[cfg(test)]
1494 mod decreasing_mixnode_pledge {
1495 use mixnet_contract_common::mixnode::PendingMixNodeChanges;
1496 use mixnet_contract_common::{EpochState, EpochStatus};
1497
1498 use crate::mixnodes::helpers::tests::{
1499 setup_mix_combinations, OWNER_UNBONDED, OWNER_UNBONDED_LEFTOVER, OWNER_UNBONDING,
1500 };
1501 use crate::support::tests::test_helpers::TestSetup;
1502
1503 use super::*;
1504
1505 #[test]
1506 fn cant_be_performed_if_epoch_transition_is_in_progress() {
1507 let bad_states = vec![
1508 EpochState::Rewarding {
1509 last_rewarded: 0,
1510 final_node_id: 0,
1511 },
1512 EpochState::ReconcilingEvents,
1513 EpochState::AdvancingEpoch,
1514 ];
1515
1516 for bad_state in bad_states {
1517 let mut test = TestSetup::new();
1518 let env = test.env();
1519 let owner = "mix-owner";
1520 let decrease = test.coin(1000);
1521
1522 test.add_dummy_mixnode(owner, Some(Uint128::new(100_000_000_000)));
1523
1524 let mut status = EpochStatus::new(test.rewarding_validator().sender);
1525 status.state = bad_state;
1526 interval_storage::save_current_epoch_status(test.deps_mut().storage, &status)
1527 .unwrap();
1528
1529 let sender = mock_info(owner, &[]);
1530 let res = try_decrease_pledge(test.deps_mut(), env, sender, decrease);
1531
1532 assert!(matches!(
1533 res,
1534 Err(MixnetContractError::EpochAdvancementInProgress { .. })
1535 ));
1536 }
1537 }
1538
1539 #[test]
1540 fn is_not_allowed_if_account_doesnt_own_mixnode() {
1541 let mut test = TestSetup::new();
1542 let env = test.env();
1543 let sender = mock_info("not-mix-owner", &[]);
1544 let decrease = test.coin(1000);
1545
1546 let res = try_decrease_pledge(test.deps_mut(), env, sender, decrease);
1547 assert_eq!(
1548 res,
1549 Err(MixnetContractError::NoAssociatedMixNodeBond {
1550 owner: Addr::unchecked("not-mix-owner")
1551 })
1552 )
1553 }
1554
1555 #[test]
1556 fn is_not_allowed_if_theres_proxy_mismatch() {
1557 let mut test = TestSetup::new();
1558 let env = test.env();
1559
1560 let owner_without_proxy = Addr::unchecked("no-proxy");
1561 let owner_with_proxy = Addr::unchecked("with-proxy");
1562 let proxy = Addr::unchecked("proxy");
1563 let wrong_proxy = Addr::unchecked("unrelated-proxy");
1564
1565 let stake = Uint128::new(100_000_000_000);
1567 let decrease = test.coin(1000);
1568
1569 test.add_dummy_mixnode(owner_without_proxy.as_str(), Some(stake));
1570 test.add_dummy_mixnode_with_illegal_proxy(
1571 owner_with_proxy.as_str(),
1572 Some(stake),
1573 proxy.clone(),
1574 );
1575
1576 let res = _try_decrease_pledge(
1577 test.deps_mut(),
1578 env.clone(),
1579 decrease.clone(),
1580 owner_without_proxy.clone(),
1581 Some(proxy),
1582 );
1583 assert_eq!(
1584 res,
1585 Err(MixnetContractError::ProxyMismatch {
1586 existing: "None".to_string(),
1587 incoming: "proxy".to_string(),
1588 })
1589 );
1590
1591 let res = _try_decrease_pledge(
1592 test.deps_mut(),
1593 env.clone(),
1594 decrease.clone(),
1595 owner_with_proxy.clone(),
1596 None,
1597 );
1598 assert_eq!(
1599 res,
1600 Err(MixnetContractError::ProxyMismatch {
1601 existing: "proxy".to_string(),
1602 incoming: "None".to_string(),
1603 })
1604 );
1605
1606 let res = _try_decrease_pledge(
1607 test.deps_mut(),
1608 env,
1609 decrease,
1610 owner_with_proxy.clone(),
1611 Some(wrong_proxy),
1612 );
1613 assert_eq!(
1614 res,
1615 Err(MixnetContractError::ProxyMismatch {
1616 existing: "proxy".to_string(),
1617 incoming: "unrelated-proxy".to_string(),
1618 })
1619 )
1620 }
1621
1622 #[test]
1623 fn is_not_allowed_if_mixnode_has_unbonded_or_is_unbonding() {
1624 let mut test = TestSetup::new();
1625 let env = test.env();
1626
1627 let stake = Uint128::new(100_000_000_000);
1629 let decrease = test.coin(1000);
1630
1631 let owner_unbonding = Addr::unchecked(OWNER_UNBONDING);
1634 let owner_unbonded = Addr::unchecked(OWNER_UNBONDED);
1635 let owner_unbonded_leftover = Addr::unchecked(OWNER_UNBONDED_LEFTOVER);
1636
1637 let ids = setup_mix_combinations(&mut test, Some(stake));
1638 let mix_id_unbonding = ids[1].mix_id;
1639
1640 let res = try_decrease_pledge(
1641 test.deps_mut(),
1642 env.clone(),
1643 mock_info(owner_unbonding.as_str(), &[]),
1644 decrease.clone(),
1645 );
1646 assert_eq!(
1647 res,
1648 Err(MixnetContractError::MixnodeIsUnbonding {
1649 mix_id: mix_id_unbonding
1650 })
1651 );
1652
1653 let res = try_decrease_pledge(
1656 test.deps_mut(),
1657 env.clone(),
1658 mock_info(owner_unbonded_leftover.as_str(), &[]),
1659 decrease.clone(),
1660 );
1661 assert_eq!(
1662 res,
1663 Err(MixnetContractError::NoAssociatedMixNodeBond {
1664 owner: owner_unbonded_leftover
1665 })
1666 );
1667
1668 let res = try_decrease_pledge(
1669 test.deps_mut(),
1670 env,
1671 mock_info(owner_unbonded.as_str(), &[]),
1672 decrease,
1673 );
1674 assert_eq!(
1675 res,
1676 Err(MixnetContractError::NoAssociatedMixNodeBond {
1677 owner: owner_unbonded
1678 })
1679 )
1680 }
1681
1682 #[test]
1683 fn is_not_allowed_if_it_would_result_going_below_minimum_pledge() {
1684 let mut test = TestSetup::new();
1685 let env = test.env();
1686 let owner = "mix-owner";
1687
1688 let minimum_pledge = minimum_mixnode_pledge(test.deps().storage).unwrap();
1689 let pledge_amount = minimum_pledge.amount + Uint128::new(100);
1690 let pledged = test.coin(pledge_amount.u128());
1691 test.add_dummy_mixnode(owner, Some(pledge_amount));
1692
1693 let invalid_decrease = test.coin(150);
1694 let valid_decrease = test.coin(50);
1695
1696 let sender = mock_info(owner, &[]);
1697 let res = try_decrease_pledge(
1698 test.deps_mut(),
1699 env.clone(),
1700 sender.clone(),
1701 invalid_decrease.clone(),
1702 );
1703 assert_eq!(
1704 res,
1705 Err(MixnetContractError::InvalidPledgeReduction {
1706 current: pledged.amount,
1707 decrease_by: invalid_decrease.amount,
1708 minimum: minimum_pledge.amount,
1709 denom: minimum_pledge.denom,
1710 })
1711 );
1712
1713 let res = try_decrease_pledge(test.deps_mut(), env, sender, valid_decrease);
1714 assert!(res.is_ok())
1715 }
1716
1717 #[test]
1718 fn provided_amount_has_to_be_nonzero() {
1719 let mut test = TestSetup::new();
1720 let env = test.env();
1721
1722 let stake = Uint128::new(100_000_000_000);
1723 let decrease = test.coin(0);
1724
1725 let owner = "mix-owner";
1726 test.add_dummy_mixnode(owner, Some(stake));
1727
1728 let sender = mock_info(owner, &[]);
1729 let res = try_decrease_pledge(test.deps_mut(), env, sender, decrease);
1730 assert_eq!(res, Err(MixnetContractError::ZeroCoinAmount))
1731 }
1732
1733 #[test]
1734 fn is_not_allowed_if_there_are_pending_pledge_changes() {
1735 let mut test = TestSetup::new();
1736 let env = test.env();
1737 let stake = Uint128::new(100_000_000_000);
1738 let decrease = test.coin(1000);
1739
1740 let owner = "mix-owner1";
1742 test.add_dummy_mixnode(owner, Some(stake));
1743 let sender = mock_info(owner, &[test.coin(1000)]);
1744 try_increase_pledge(test.deps_mut(), env.clone(), sender.clone()).unwrap();
1745
1746 let res = try_decrease_pledge(test.deps_mut(), env.clone(), sender, decrease.clone());
1747 assert_eq!(
1748 res,
1749 Err(MixnetContractError::PendingPledgeChange {
1750 pending_event_id: 1
1751 })
1752 );
1753
1754 let owner = "mix-owner2";
1756 test.add_dummy_mixnode(owner, Some(stake));
1757 let sender = mock_info(owner, &[]);
1758 let amount = test.coin(1000);
1759 try_decrease_pledge(test.deps_mut(), env.clone(), sender, amount).unwrap();
1760
1761 let sender = mock_info(owner, &[test.coin(1000)]);
1762 let res = try_decrease_pledge(test.deps_mut(), env.clone(), sender, decrease.clone());
1763 assert_eq!(
1764 res,
1765 Err(MixnetContractError::PendingPledgeChange {
1766 pending_event_id: 2
1767 })
1768 );
1769
1770 let owner = "mix-owner3";
1772 let mix_id = test.add_dummy_mixnode(owner, Some(stake));
1773 let pending_change = PendingMixNodeChanges {
1774 pledge_change: Some(1234),
1775 };
1776 storage::PENDING_MIXNODE_CHANGES
1777 .save(test.deps_mut().storage, mix_id, &pending_change)
1778 .unwrap();
1779
1780 let sender = mock_info(owner, &[test.coin(1000)]);
1781 let res = try_decrease_pledge(test.deps_mut(), env, sender, decrease);
1782 assert_eq!(
1783 res,
1784 Err(MixnetContractError::PendingPledgeChange {
1785 pending_event_id: 1234
1786 })
1787 );
1788 }
1789
1790 #[test]
1791 fn with_valid_information_creates_pending_event() {
1792 let mut test = TestSetup::new();
1793 let env = test.env();
1794
1795 let stake = Uint128::new(100_000_000_000);
1797 let decrease = test.coin(1000);
1798
1799 let owner = "mix-owner";
1800 let mix_id = test.add_dummy_mixnode(owner, Some(stake));
1801
1802 let events = test.pending_epoch_events();
1803 assert!(events.is_empty());
1804
1805 let sender = mock_info(owner, &[]);
1806 try_decrease_pledge(test.deps_mut(), env, sender, decrease.clone()).unwrap();
1807
1808 let events = test.pending_epoch_events();
1809
1810 assert_eq!(
1811 events[0].kind,
1812 PendingEpochEventKind::DecreasePledge {
1813 mix_id,
1814 decrease_by: decrease,
1815 }
1816 );
1817 }
1818
1819 #[test]
1820 fn fails_for_illegal_proxy() {
1821 let mut test = TestSetup::new();
1822 let env = test.env();
1823
1824 let stake = Uint128::new(100_000_000_000);
1825 let decrease = test.coin(1000);
1826
1827 let illegal_proxy = Addr::unchecked("not-vesting-contract");
1828 let vesting_contract = test.vesting_contract();
1829
1830 let owner = "alice";
1831
1832 test.add_dummy_mixnode_with_illegal_proxy(owner, Some(stake), illegal_proxy.clone());
1833
1834 let res = try_decrease_pledge_on_behalf(
1835 test.deps_mut(),
1836 env,
1837 mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
1838 decrease,
1839 owner.to_string(),
1840 )
1841 .unwrap_err();
1842
1843 assert_eq!(
1844 res,
1845 MixnetContractError::SenderIsNotVestingContract {
1846 received: illegal_proxy,
1847 vesting_contract,
1848 }
1849 )
1850 }
1851 }
1852}