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
121
    fn cmp(&self, other: &Self) -> Ordering {
52
121
        self.stake
53
121
            .cmp(&other.stake)
54
121
            .reverse()
55
121
            .then_with(|| self.candidate.cmp(&other.candidate))
56
121
    }
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
208
    pub fn add_total_stake(
73
208
        candidate: &Candidate<T>,
74
208
        stake: &Stake<T::Balance>,
75
208
    ) -> Result<(), Error<T>> {
76
208
        if stake.0.is_zero() {
77
22
            return Ok(());
78
186
        }
79

            
80
186
        let new_stake = Self::total_stake(candidate).0.err_add(&stake.0)?;
81
186
        Self::update_total_stake(candidate, Stake(new_stake))?;
82

            
83
186
        Ok(())
84
208
    }
85

            
86
163
    pub fn sub_total_stake(
87
163
        candidate: &Candidate<T>,
88
163
        stake: Stake<T::Balance>,
89
163
    ) -> Result<(), Error<T>> {
90
163
        if stake.0.is_zero() {
91
113
            return Ok(());
92
50
        }
93

            
94
50
        let new_stake = Self::total_stake(candidate).0.err_sub(&stake.0)?;
95
50
        Self::update_total_stake(candidate, Stake(new_stake))?;
96

            
97
50
        Ok(())
98
163
    }
99

            
100
249
    pub fn update_total_stake(
101
249
        candidate: &Candidate<T>,
102
249
        new_stake: Stake<T::Balance>,
103
249
    ) -> Result<(), Error<T>> {
104
249
        let stake_before = Pools::<T>::get(candidate, &PoolsKey::CandidateTotalStake);
105
249
        Pools::<T>::set(candidate, &PoolsKey::CandidateTotalStake, new_stake.0);
106

            
107
        // Compute self delegation.
108
249
        let ac_self = if pools::AutoCompounding::<T>::shares_supply(candidate)
109
249
            .0
110
249
            .is_zero()
111
        {
112
208
            Zero::zero()
113
        } else {
114
41
            let shares = pools::AutoCompounding::<T>::shares(candidate, candidate);
115
41
            pools::AutoCompounding::shares_to_stake(candidate, shares)?.0
116
        };
117

            
118
249
        let mr_self = if pools::ManualRewards::<T>::shares_supply(candidate)
119
249
            .0
120
249
            .is_zero()
121
        {
122
219
            Zero::zero()
123
        } else {
124
30
            let shares = pools::ManualRewards::<T>::shares(candidate, candidate);
125
30
            pools::ManualRewards::shares_to_stake(candidate, shares)?.0
126
        };
127

            
128
249
        let joining_self = if pools::Joining::<T>::shares_supply(candidate).0.is_zero() {
129
72
            Zero::zero()
130
        } else {
131
177
            let shares = pools::Joining::<T>::shares(candidate, candidate);
132
177
            pools::Joining::shares_to_stake(candidate, shares)?.0
133
        };
134

            
135
249
        let self_delegation = ac_self.err_add(&mr_self)?.err_add(&joining_self)?;
136

            
137
249
        let mut list = SortedEligibleCandidates::<T>::get();
138

            
139
        // Remove old data if it exists.
140
249
        let old_position = match list.binary_search(&EligibleCandidate {
141
249
            candidate: candidate.clone(),
142
249
            stake: stake_before,
143
249
        }) {
144
63
            Ok(pos) => {
145
63
                let _ = list.remove(pos);
146
63
                Some(pos as u32)
147
            }
148
186
            Err(_) => None,
149
        };
150

            
151
249
        let eligible = self_delegation >= T::MinimumSelfDelegation::get()
152
141
            && T::EligibleCandidatesFilter::is_candidate_eligible(candidate);
153

            
154
        // Find new position in the sorted list.
155
        // It will not be inserted if under the minimum self delegation.
156
249
        let new_position = if eligible {
157
129
            let entry = EligibleCandidate {
158
129
                candidate: candidate.clone(),
159
129
                stake: new_stake.0,
160
129
            };
161

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

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

            
180
128
                Some(pos as u32)
181
            }
182
        } else {
183
120
            None
184
        };
185

            
186
        // Only emit event if position actually changed.
187
249
        if old_position != new_position {
188
120
            Pallet::<T>::deposit_event(Event::<T>::UpdatedCandidatePosition {
189
120
                candidate: candidate.clone(),
190
120
                stake: new_stake.0,
191
120
                self_delegation,
192
120
                before: old_position,
193
120
                after: new_position,
194
120
            });
195
195
        }
196

            
197
249
        SortedEligibleCandidates::<T>::set(list);
198

            
199
249
        Ok(())
200
249
    }
201
}