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
    serde::{Deserialize, Serialize},
27
    sp_core::{Get, MaxEncodedLen, RuntimeDebug},
28
    sp_runtime::traits::Zero,
29
    tp_maths::{ErrAdd, ErrSub},
30
};
31

            
32
/// Eligible candidate with its stake.
33
#[derive(
34
    RuntimeDebug,
35
    PartialEq,
36
    Eq,
37
    Encode,
38
    Decode,
39
    Clone,
40
1248
    TypeInfo,
41
    Serialize,
42
    Deserialize,
43
    MaxEncodedLen,
44
)]
45
pub struct EligibleCandidate<C, S> {
46
    pub candidate: C,
47
    pub stake: S,
48
}
49

            
50
impl<C: Ord, S: Ord> Ord for EligibleCandidate<C, S> {
51
228
    fn cmp(&self, other: &Self) -> Ordering {
52
228
        self.stake
53
228
            .cmp(&other.stake)
54
228
            .reverse()
55
228
            .then_with(|| self.candidate.cmp(&other.candidate))
56
228
    }
57
}
58

            
59
impl<C: Ord, S: Ord> PartialOrd for EligibleCandidate<C, S> {
60
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
61
        Some(self.cmp(other))
62
    }
63
}
64

            
65
pub struct Candidates<T>(PhantomData<T>);
66

            
67
impl<T: Config> Candidates<T> {
68
631
    pub fn total_stake(candidate: &Candidate<T>) -> Stake<T::Balance> {
69
631
        Stake(Pools::<T>::get(candidate, &PoolsKey::CandidateTotalStake))
70
631
    }
71

            
72
1372
    pub fn add_total_stake(
73
1372
        candidate: &Candidate<T>,
74
1372
        stake: &Stake<T::Balance>,
75
1372
    ) -> Result<(), Error<T>> {
76
1372
        if stake.0.is_zero() {
77
1075
            return Ok(());
78
297
        }
79

            
80
297
        let new_stake = Self::total_stake(candidate).0.err_add(&stake.0)?;
81

            
82
297
        Pallet::<T>::deposit_event(Event::<T>::IncreasedStake {
83
297
            candidate: candidate.clone(),
84
297
            stake_diff: stake.0,
85
297
        });
86
297

            
87
297
        Self::update_total_stake(candidate, Stake(new_stake))?;
88

            
89
297
        Ok(())
90
1372
    }
91

            
92
219
    pub fn sub_total_stake(
93
219
        candidate: &Candidate<T>,
94
219
        stake: Stake<T::Balance>,
95
219
    ) -> Result<(), Error<T>> {
96
219
        if stake.0.is_zero() {
97
163
            return Ok(());
98
56
        }
99

            
100
56
        let new_stake = Self::total_stake(candidate).0.err_sub(&stake.0)?;
101

            
102
56
        Pallet::<T>::deposit_event(Event::<T>::DecreasedStake {
103
56
            candidate: candidate.clone(),
104
56
            stake_diff: stake.0,
105
56
        });
106
56

            
107
56
        Self::update_total_stake(candidate, Stake(new_stake))?;
108

            
109
56
        Ok(())
110
219
    }
111

            
112
369
    pub fn update_total_stake(
113
369
        candidate: &Candidate<T>,
114
369
        new_stake: Stake<T::Balance>,
115
369
    ) -> Result<(), Error<T>> {
116
369
        let stake_before = Pools::<T>::get(candidate, &PoolsKey::CandidateTotalStake);
117
369
        Pools::<T>::set(candidate, &PoolsKey::CandidateTotalStake, new_stake.0);
118

            
119
        // Compute self delegation.
120
369
        let ac_self = if pools::AutoCompounding::<T>::shares_supply(candidate)
121
369
            .0
122
369
            .is_zero()
123
        {
124
317
            Zero::zero()
125
        } else {
126
52
            let shares = pools::AutoCompounding::<T>::shares(candidate, candidate);
127
52
            pools::AutoCompounding::shares_to_stake(candidate, shares)?.0
128
        };
129

            
130
369
        let mr_self = if pools::ManualRewards::<T>::shares_supply(candidate)
131
369
            .0
132
369
            .is_zero()
133
        {
134
329
            Zero::zero()
135
        } else {
136
40
            let shares = pools::ManualRewards::<T>::shares(candidate, candidate);
137
40
            pools::ManualRewards::shares_to_stake(candidate, shares)?.0
138
        };
139

            
140
369
        let joining_self = if pools::Joining::<T>::shares_supply(candidate).0.is_zero() {
141
89
            Zero::zero()
142
        } else {
143
280
            let shares = pools::Joining::<T>::shares(candidate, candidate);
144
280
            pools::Joining::shares_to_stake(candidate, shares)?.0
145
        };
146

            
147
369
        let self_delegation = ac_self.err_add(&mr_self)?.err_add(&joining_self)?;
148

            
149
369
        let mut list = SortedEligibleCandidates::<T>::get();
150

            
151
        // Remove old data if it exists.
152
369
        let old_position = match list.binary_search(&EligibleCandidate {
153
369
            candidate: candidate.clone(),
154
369
            stake: stake_before,
155
369
        }) {
156
90
            Ok(pos) => {
157
90
                let _ = list.remove(pos);
158
90
                Some(pos as u32)
159
            }
160
279
            Err(_) => None,
161
        };
162

            
163
369
        let eligible = self_delegation >= T::MinimumSelfDelegation::get()
164
231
            && T::EligibleCandidatesFilter::is_candidate_eligible(candidate);
165

            
166
        // Find new position in the sorted list.
167
        // It will not be inserted if under the minimum self delegation.
168
369
        let new_position = if eligible {
169
215
            let entry = EligibleCandidate {
170
215
                candidate: candidate.clone(),
171
215
                stake: new_stake.0,
172
215
            };
173

            
174
            // Candidate should not appear in the list, we're instead searching where
175
            // to insert it.
176
215
            let Err(pos) = list.binary_search(&entry) else {
177
                return Err(Error::<T>::InconsistentState);
178
            };
179

            
180
215
            if pos >= T::EligibleCandidatesBufferSize::get() as usize {
181
1
                None
182
            } else {
183
                // Insert in correct position then truncate the list if necessary.
184
214
                list = list
185
214
                    .try_mutate(move |list| {
186
214
                        list.insert(pos, entry.clone());
187
214
                        list.truncate(T::EligibleCandidatesBufferSize::get() as usize)
188
214
                    })
189
214
                    // This should not occur as we truncate the list above.
190
214
                    .ok_or(Error::<T>::InconsistentState)?;
191

            
192
214
                Some(pos as u32)
193
            }
194
        } else {
195
154
            None
196
        };
197

            
198
369
        Pallet::<T>::deposit_event(Event::<T>::UpdatedCandidatePosition {
199
369
            candidate: candidate.clone(),
200
369
            stake: new_stake.0,
201
369
            self_delegation,
202
369
            before: old_position,
203
369
            after: new_position,
204
369
        });
205
369

            
206
369
        SortedEligibleCandidates::<T>::set(list);
207
369

            
208
369
        Ok(())
209
369
    }
210
}