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
1236
    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
210
    fn cmp(&self, other: &Self) -> Ordering {
52
210
        self.stake
53
210
            .cmp(&other.stake)
54
210
            .reverse()
55
210
            .then_with(|| self.candidate.cmp(&other.candidate))
56
210
    }
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
607
    pub fn total_stake(candidate: &Candidate<T>) -> Stake<T::Balance> {
69
607
        Stake(Pools::<T>::get(candidate, &PoolsKey::CandidateTotalStake))
70
607
    }
71

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

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

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

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

            
89
285
        Ok(())
90
1306
    }
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
345
    pub fn update_total_stake(
113
345
        candidate: &Candidate<T>,
114
345
        new_stake: Stake<T::Balance>,
115
345
    ) -> Result<(), Error<T>> {
116
345
        let stake_before = Pools::<T>::get(candidate, &PoolsKey::CandidateTotalStake);
117
345
        Pools::<T>::set(candidate, &PoolsKey::CandidateTotalStake, new_stake.0);
118

            
119
        // Compute self delegation.
120
345
        let ac_self = if pools::AutoCompounding::<T>::shares_supply(candidate)
121
345
            .0
122
345
            .is_zero()
123
        {
124
293
            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
345
        let mr_self = if pools::ManualRewards::<T>::shares_supply(candidate)
131
345
            .0
132
345
            .is_zero()
133
        {
134
305
            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
345
        let joining_self = if pools::Joining::<T>::shares_supply(candidate).0.is_zero() {
141
89
            Zero::zero()
142
        } else {
143
256
            let shares = pools::Joining::<T>::shares(candidate, candidate);
144
256
            pools::Joining::shares_to_stake(candidate, shares)?.0
145
        };
146

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

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

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

            
163
345
        let eligible = self_delegation >= T::MinimumSelfDelegation::get()
164
207
            && 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
345
        let new_position = if eligible {
169
201
            let entry = EligibleCandidate {
170
201
                candidate: candidate.clone(),
171
201
                stake: new_stake.0,
172
201
            };
173

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

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

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

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

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

            
208
345
        Ok(())
209
345
    }
210
}