1
// Copyright (C) Moondance Labs Ltd.
2
// This file is part of Tanssi.
3

            
4
// Tanssi is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Tanssi is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Tanssi.  If not, see <http://www.gnu.org/licenses/>
16

            
17
use {
18
    crate::{
19
        pools::{self, Pool},
20
        traits::IsCandidateEligible,
21
        Candidate, Config, Error, Event, Pallet, Pools, PoolsKey, SortedEligibleCandidates, Stake,
22
    },
23
    core::{cmp::Ordering, marker::PhantomData},
24
    parity_scale_codec::{Decode, Encode},
25
    scale_info::TypeInfo,
26
    sp_core::{Get, RuntimeDebug},
27
    sp_runtime::traits::Zero,
28
    tp_maths::{ErrAdd, ErrSub},
29
};
30

            
31
#[cfg(feature = "std")]
32
use serde::{Deserialize, Serialize};
33

            
34
/// Eligible candidate with its stake.
35
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
36
528
#[derive(RuntimeDebug, PartialEq, Eq, Encode, Decode, Clone, TypeInfo)]
37
pub struct EligibleCandidate<C, S> {
38
    pub candidate: C,
39
    pub stake: S,
40
}
41

            
42
impl<C: Ord, S: Ord> Ord for EligibleCandidate<C, S> {
43
88
    fn cmp(&self, other: &Self) -> Ordering {
44
88
        self.stake
45
88
            .cmp(&other.stake)
46
88
            .reverse()
47
88
            .then_with(|| self.candidate.cmp(&other.candidate))
48
88
    }
49
}
50

            
51
impl<C: Ord, S: Ord> PartialOrd for EligibleCandidate<C, S> {
52
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
53
        Some(self.cmp(other))
54
    }
55
}
56

            
57
pub struct Candidates<T>(PhantomData<T>);
58

            
59
impl<T: Config> Candidates<T> {
60
439
    pub fn total_stake(candidate: &Candidate<T>) -> Stake<T::Balance> {
61
439
        Stake(Pools::<T>::get(candidate, &PoolsKey::CandidateTotalStake))
62
439
    }
63

            
64
516
    pub fn add_total_stake(
65
516
        candidate: &Candidate<T>,
66
516
        stake: &Stake<T::Balance>,
67
516
    ) -> Result<(), Error<T>> {
68
516
        if stake.0.is_zero() {
69
365
            return Ok(());
70
151
        }
71

            
72
151
        let new_stake = Self::total_stake(candidate).0.err_add(&stake.0)?;
73

            
74
151
        Pallet::<T>::deposit_event(Event::<T>::IncreasedStake {
75
151
            candidate: candidate.clone(),
76
151
            stake_diff: stake.0,
77
151
        });
78
151

            
79
151
        Self::update_total_stake(candidate, Stake(new_stake))?;
80

            
81
151
        Ok(())
82
516
    }
83

            
84
121
    pub fn sub_total_stake(
85
121
        candidate: &Candidate<T>,
86
121
        stake: Stake<T::Balance>,
87
121
    ) -> Result<(), Error<T>> {
88
121
        if stake.0.is_zero() {
89
85
            return Ok(());
90
36
        }
91

            
92
36
        let new_stake = Self::total_stake(candidate).0.err_sub(&stake.0)?;
93

            
94
36
        Pallet::<T>::deposit_event(Event::<T>::DecreasedStake {
95
36
            candidate: candidate.clone(),
96
36
            stake_diff: stake.0,
97
36
        });
98
36

            
99
36
        Self::update_total_stake(candidate, Stake(new_stake))?;
100

            
101
36
        Ok(())
102
121
    }
103

            
104
189
    pub fn update_total_stake(
105
189
        candidate: &Candidate<T>,
106
189
        new_stake: Stake<T::Balance>,
107
189
    ) -> Result<(), Error<T>> {
108
189
        let stake_before = Pools::<T>::get(candidate, &PoolsKey::CandidateTotalStake);
109
189
        Pools::<T>::set(candidate, &PoolsKey::CandidateTotalStake, new_stake.0);
110

            
111
        // Compute self delegation.
112
189
        let ac_self = if pools::AutoCompounding::<T>::shares_supply(candidate)
113
189
            .0
114
189
            .is_zero()
115
        {
116
151
            Zero::zero()
117
        } else {
118
38
            let shares = pools::AutoCompounding::<T>::shares(candidate, candidate);
119
38
            pools::AutoCompounding::shares_to_stake(candidate, shares)?.0
120
        };
121

            
122
189
        let mr_self = if pools::ManualRewards::<T>::shares_supply(candidate)
123
189
            .0
124
189
            .is_zero()
125
        {
126
157
            Zero::zero()
127
        } else {
128
32
            let shares = pools::ManualRewards::<T>::shares(candidate, candidate);
129
32
            pools::ManualRewards::shares_to_stake(candidate, shares)?.0
130
        };
131

            
132
189
        let joining_self = if pools::Joining::<T>::shares_supply(candidate).0.is_zero() {
133
61
            Zero::zero()
134
        } else {
135
128
            let shares = pools::Joining::<T>::shares(candidate, candidate);
136
128
            pools::Joining::shares_to_stake(candidate, shares)?.0
137
        };
138

            
139
189
        let self_delegation = ac_self.err_add(&mr_self)?.err_add(&joining_self)?;
140

            
141
189
        let mut list = SortedEligibleCandidates::<T>::get();
142

            
143
        // Remove old data if it exists.
144
189
        let old_position = match list.binary_search(&EligibleCandidate {
145
189
            candidate: candidate.clone(),
146
189
            stake: stake_before,
147
189
        }) {
148
44
            Ok(pos) => {
149
44
                let _ = list.remove(pos);
150
44
                Some(pos as u32)
151
            }
152
145
            Err(_) => None,
153
        };
154

            
155
189
        let eligible = self_delegation >= T::MinimumSelfDelegation::get()
156
97
            && T::EligibleCandidatesFilter::is_candidate_eligible(candidate);
157

            
158
        // Find new position in the sorted list.
159
        // It will not be inserted if under the minimum self delegation.
160
189
        let new_position = if eligible {
161
95
            let entry = EligibleCandidate {
162
95
                candidate: candidate.clone(),
163
95
                stake: new_stake.0,
164
95
            };
165

            
166
            // Candidate should not appear in the list, we're instead searching where
167
            // to insert it.
168
95
            let Err(pos) = list.binary_search(&entry) else {
169
                return Err(Error::<T>::InconsistentState);
170
            };
171

            
172
95
            if pos >= T::EligibleCandidatesBufferSize::get() as usize {
173
1
                None
174
            } else {
175
                // Insert in correct position then truncate the list if necessary.
176
94
                list = list
177
94
                    .try_mutate(move |list| {
178
94
                        list.insert(pos, entry.clone());
179
94
                        list.truncate(T::EligibleCandidatesBufferSize::get() as usize)
180
94
                    })
181
94
                    // This should not occur as we truncate the list above.
182
94
                    .ok_or(Error::<T>::InconsistentState)?;
183

            
184
94
                Some(pos as u32)
185
            }
186
        } else {
187
94
            None
188
        };
189

            
190
189
        Pallet::<T>::deposit_event(Event::<T>::UpdatedCandidatePosition {
191
189
            candidate: candidate.clone(),
192
189
            stake: new_stake.0,
193
189
            self_delegation,
194
189
            before: old_position,
195
189
            after: new_position,
196
189
        });
197
189

            
198
189
        SortedEligibleCandidates::<T>::set(list);
199
189

            
200
189
        Ok(())
201
189
    }
202
}