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

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

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

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

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

            
89
219
        Ok(())
90
1240
    }
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
117
            return Ok(());
98
40
        }
99

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

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

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

            
109
40
        Ok(())
110
157
    }
111

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

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

            
130
261
        let mr_self = if pools::ManualRewards::<T>::shares_supply(candidate)
131
261
            .0
132
261
            .is_zero()
133
        {
134
221
            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
261
        let joining_self = if pools::Joining::<T>::shares_supply(candidate).0.is_zero() {
141
73
            Zero::zero()
142
        } else {
143
188
            let shares = pools::Joining::<T>::shares(candidate, candidate);
144
188
            pools::Joining::shares_to_stake(candidate, shares)?.0
145
        };
146

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

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

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

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

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

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

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

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

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

            
208
261
        Ok(())
209
261
    }
210
}