mixnet_contract/mixnodes/
transactions.rs

1// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4use 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// I'm not entirely sure how to deal with this warning at the current moment
110//
111// TODO: perhaps also require the user to explicitly provide what it thinks is the current nonce
112// so that we could return a better error message if it doesn't match?
113#[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    // check if the pledge contains any funds of the appropriate denomination
125    let minimum_pledge = mixnet_params_storage::minimum_mixnode_pledge(deps.storage)?;
126    let pledge = validate_pledge(pledge, minimum_pledge)?;
127
128    // if the client has an active bonded mixnode or gateway, don't allow bonding
129    // note that this has to be done explicitly as `UniqueIndex` constraint would not protect us
130    // against attempting to use different node types (i.e. gateways and mixnodes)
131    ensure_no_existing_bond(&owner, deps.storage)?;
132
133    // there's no need to explicitly check whether there already exists mixnode with the same
134    // identity or sphinx keys as this is going to be done implicitly when attempting to save
135    // the bond information due to `UniqueIndex` constraint defined on those fields.
136
137    // check if this sender actually owns the mixnode by checking the signature
138    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    // update the signing nonce associated with this sender so that the future signature would be made on the new value
149    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    // increasing pledge is only allowed if the epoch is currently not in the process of being advanced
206    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    // push the event to execute it at the end of the epoch
218    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    // decreasing pledge is only allowed if the epoch is currently not in the process of being advanced
265    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    // check that the denomination is correct
274    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    // also check if the request contains non-zero amount
282    // (otherwise it's a no-op and we should we waste gas when resolving events?)
283    if decrease_by.amount.is_zero() {
284        return Err(MixnetContractError::ZeroCoinAmount);
285    }
286
287    // decreasing pledge can't result in the new pledge being lower than the minimum amount
288    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    // push the event to execute it at the end of the epoch
304    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    // unbonding is only allowed if the epoch is currently not in the process of being advanced
348    ensure_epoch_in_progress_state(deps.storage)?;
349
350    // see if the proxy matches
351    ensure_proxy_match(&proxy, &existing_bond.proxy)?;
352    ensure_bonded(&existing_bond)?;
353
354    // if there are any pending requests to change the pledge, wait for them to resolve before allowing the unbonding
355    ensure_no_pending_pledge_changes(&pending_changes)?;
356
357    // set `is_unbonding` field
358    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    // push the event to execute it at the end of the epoch
368    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    // see if the node still exists
468    let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?;
469
470    // changing cost params is only allowed if the epoch is currently not in the process of being advanced
471    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    // push the interval event
484    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        // if we don't send enough funds
523        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        // we are informed that we didn't send enough funds
529        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        // if the signature provided is invalid, the bonding also fails
546        let info = mock_info(sender, &[minimum_pledge]);
547
548        // if there was already a mixnode bonded by particular user
549        test.add_dummy_mixnode(sender, None);
550
551        // it fails
552        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        // the same holds if the user already owns a gateway
563        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        // but after he unbonds it, it's all fine again
581        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        // make sure we got assigned the next id (note: we have already bonded a mixnode before in this test)
588        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        // and make sure we're on layer 2 (because it was the next empty one)
593        assert_eq!(Layer::Two, bond.layer);
594
595        // and see if the layer distribution matches our expectation
596        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        // the above using cost params fixture
615        let cost_params = fixtures::mix_node_cost_params_fixture();
616
617        // using different parameters than what the signature was made on
618        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        // even stake amount is protected
631        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        // trying to reuse the same signature for another bonding fails (because nonce doesn't match!)
657        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        // trying to remove your mixnode fails if you never had one in the first place
753        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        // attempted to remove on behalf with invalid proxy (current is `None`)
765        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        // "normal" unbonding succeeds and unbonding event is pushed to the pending epoch events
781        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        // but fails if repeated (since the node is already in the "unbonding" state)(
796        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        // prior increase
835        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        // prior decrease
849        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        // artificial event
865        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        // try updating a non existing mixnode bond
900        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        // attempted to remove on behalf with invalid proxy (current is `None`)
912        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        // "normal" update succeeds
926        let res = try_update_mixnode_config(test.deps_mut(), info.clone(), update.clone());
927        assert!(res.is_ok());
928
929        // and the config has actually been updated
930        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        // but we cannot perform any updates whilst the mixnode is already unbonding
939        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        // try updating a non existing mixnode bond
1029        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        // attempted to remove on behalf with invalid proxy (current is `None`)
1046        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        // "normal" update succeeds
1061        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        // see if the event has been pushed onto the queue
1070        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        // execute the event
1086        test_helpers::execute_all_pending_events(test.deps_mut(), env.clone());
1087
1088        // and see if the config has actually been updated
1089        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        // but we cannot perform any updates whilst the mixnode is already unbonding
1095        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        // change identity but reuse sphinx key
1156        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        // change identity but reuse sphinx key
1180        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            // TODO: I dislike this cross-test access, but it provides us with exactly what we need
1314            // perhaps it should be refactored a bit?
1315            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            // if the nodes are gone we treat them as tey never existed in the first place
1335            // (regardless of if there's some leftover data)
1336            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            // prior increase
1390            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            // prior decrease
1404            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            // artificial event
1420            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            // just to make sure that after decrease the value would still be above the minimum
1566            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            // just to make sure that after decrease the value would still be above the minimum
1628            let stake = Uint128::new(100_000_000_000);
1629            let decrease = test.coin(1000);
1630
1631            // TODO: I dislike this cross-test access, but it provides us with exactly what we need
1632            // perhaps it should be refactored a bit?
1633            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            // if the nodes are gone we treat them as tey never existed in the first place
1654            // (regardless of if there's some leftover data)
1655            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            // prior increase
1741            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            // prior decrease
1755            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            // artificial event
1771            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            // just to make sure that after decrease the value would still be above the minimum
1796            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}