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

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

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

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

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

            
89
187
        Ok(())
90
257
    }
91

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

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

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

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

            
109
50
        Ok(())
110
157
    }
111

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

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

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

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

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

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

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

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

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

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

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

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

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

            
208
250
        Ok(())
209
250
    }
210
}