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 crate::{
18
    Candidate, CandidateSummaries, CandidateSummary, Config, Delegator,
19
    DelegatorCandidateSummaries, DelegatorCandidateSummary, Pallet, PausePoolsExtrinsics, PoolKind,
20
    Pools, PoolsKey,
21
};
22
use core::marker::PhantomData;
23
use frame_support::{
24
    migrations::{MigrationId, SteppedMigration, SteppedMigrationError},
25
    pallet_prelude::{StorageVersion, Zero},
26
    traits::GetStorageVersion,
27
    weights::WeightMeter,
28
    BoundedVec, Deserialize, Serialize,
29
};
30
use parity_scale_codec::{Decode, Encode};
31
use scale_info::TypeInfo;
32
use sp_core::{ConstU32, Get, MaxEncodedLen, RuntimeDebug};
33
use sp_runtime::Saturating;
34

            
35
#[cfg(not(feature = "std"))]
36
use sp_runtime::Vec;
37

            
38
const LOG_TARGET: &'static str = "pallet_pooled_staking::migrations::stepped_generate_summaries";
39
pub const PALLET_MIGRATIONS_ID: &[u8; 21] = b"pallet-pooled-staking";
40

            
41
pub struct MigrationGenerateSummaries<T: Config>(PhantomData<T>);
42
impl<T: Config> SteppedMigration for MigrationGenerateSummaries<T> {
43
    type Cursor = MigrationState;
44
    type Identifier = MigrationId<21>;
45

            
46
2
    fn id() -> Self::Identifier {
47
2
        MigrationId {
48
2
            pallet_id: *PALLET_MIGRATIONS_ID,
49
2
            version_from: 0,
50
2
            version_to: 1,
51
2
        }
52
2
    }
53

            
54
1
    fn step(
55
1
        cursor: Option<Self::Cursor>,
56
1
        meter: &mut WeightMeter,
57
1
    ) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
58
1
        // Consumes in advance the cost of reading and writing the storage version.
59
1
        meter.consume(T::DbWeight::get().reads_writes(1, 1));
60
1

            
61
1
        if Pallet::<T>::on_chain_storage_version() != Self::id().version_from as u16 {
62
            return Ok(None);
63
1
        }
64
1

            
65
1
        // We make a weight meter with 70% of allowed weight to be extra sure the migration will not
66
1
        // cause issues
67
1
        let mut meter2 = WeightMeter::with_limit(meter.remaining() * 70 / 100);
68

            
69
1
        let new_state = stepped_generate_summaries::<
70
1
            T,
71
1
            DelegatorCandidateSummaries<T>,
72
1
            CandidateSummaries<T>,
73
1
        >(cursor, &mut meter2)?;
74
1
        if new_state.is_none() {
75
1
            // migration is finished, we update the on chain storage version
76
1
            StorageVersion::new(Self::id().version_to as u16).put::<Pallet<T>>();
77
1
        }
78

            
79
1
        meter.consume(meter2.consumed());
80
1
        Ok(new_state)
81
1
    }
82

            
83
    #[cfg(feature = "try-runtime")]
84
    fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::TryRuntimeError> {
85
        // Can we test it somehow without performing the same process? (which would be useless)
86
        Ok(Default::default())
87
    }
88

            
89
    #[cfg(feature = "try-runtime")]
90
    fn post_upgrade(_state: Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
91
        Ok(())
92
    }
93
}
94

            
95
#[derive(
96
    RuntimeDebug,
97
    PartialEq,
98
    Eq,
99
    Encode,
100
    Decode,
101
    Clone,
102
    TypeInfo,
103
    Serialize,
104
    Deserialize,
105
    MaxEncodedLen,
106
)]
107
pub struct MigrationState {
108
    /// Last key processed by the migration.
109
    /// Migration will resume from there on next step.
110
    last_raw_key: BoundedVec<u8, ConstU32<1024>>,
111
}
112

            
113
/// Generate summaries from `Pools` into provided `D` and `C`, which can be used both for
114
/// a migration (where pooled_staking was used before the introduction of summaries) and for
115
/// try_state (generate the summaries from `Pools` into separate storages, then check they
116
/// match the versions dynamically maintained).
117
1
pub fn stepped_generate_summaries<
118
1
    T: Config,
119
1
    D: frame_support::storage::StorageDoubleMap<
120
1
        Delegator<T>,
121
1
        Candidate<T>,
122
1
        DelegatorCandidateSummary,
123
1
        Query = DelegatorCandidateSummary,
124
1
    >,
125
1
    C: frame_support::storage::StorageMap<Candidate<T>, CandidateSummary, Query = CandidateSummary>,
126
1
>(
127
1
    state: Option<MigrationState>,
128
1
    meter: &mut WeightMeter,
129
1
) -> Result<Option<MigrationState>, SteppedMigrationError> {
130
1
    // If there is not yet a state, it means we're starting the migration process.
131
1
    // In this case we pause the pools extrinsics
132
1
    if state.is_none() {
133
1
        log::info!(target: LOG_TARGET, "Starting migration. Pools extrinsics will be paused until the end of the migration.");
134
1
        PausePoolsExtrinsics::<T>::put(true);
135
    } else {
136
        log::info!(target: LOG_TARGET, "Resuming migration.");
137
    }
138

            
139
    // One migration step is to read from the Pools iterator (1 read). If it is
140
    // a XXXShares PoolsKey, it will then read+write
141
    // an entry from both the candidates and delegators summaries (2 reads+writes).
142
    // We need to check before performing each step that we can consume that weight.
143
1
    let step_weight = T::DbWeight::get().reads_writes(3, 2);
144
1

            
145
1
    // If the available weight is less than the cost of 1 step, we'll never be able to migrate.
146
1
    if meter.remaining().any_lt(step_weight) {
147
        return Err(SteppedMigrationError::InsufficientWeight {
148
            required: step_weight,
149
        });
150
1
    }
151

            
152
    // Documentation if `StorageDoubleMap::iter_keys` warn sabout inserting/deleting keys
153
    // giving undefined results. While the risk is reduced by pausing the pools extrinsics,
154
    // it may happen while distributing manual rewards if this is the first time a candidate gets
155
    // manual rewards distributed (counter goes from absent to non-zero). However by looking at the
156
    // implementation of those iterators, they iterate the keys by lexicographic order. So even in
157
    // the case this entry is inserted, it will not affect iterating over the non-explored keys, and
158
    // we don't care missing reward counter entries since they are not used by the migration.
159
    //
160
    // We also prefer to use `iter_keys` over `iter` as it might optimize the POV for keys we skip.
161
1
    let mut iterator = match state {
162
        Some(MigrationState { last_raw_key }) => Pools::<T>::iter_keys_from(last_raw_key.to_vec()),
163
1
        None => Pools::<T>::iter_keys(),
164
    };
165

            
166
1
    let mut count = 0;
167

            
168
    loop {
169
5
        if !meter.can_consume(step_weight) {
170
            log::warn!(target: LOG_TARGET, "Migration limit reached. Processed {count} delegations.");
171
            return Ok(Some(MigrationState {
172
                last_raw_key: iterator
173
                    .last_raw_key()
174
                    .to_vec()
175
                    .try_into()
176
                    .expect("size should be larger than key length"),
177
            }));
178
5
        }
179
5

            
180
5
        meter.consume(T::DbWeight::get().reads_writes(1, 0));
181

            
182
5
        let Some((candidate, key)) = iterator.next() else {
183
1
            break;
184
        };
185

            
186
4
        count += 1;
187
4
        let (pool, delegator) = match key.clone() {
188
1
            PoolsKey::JoiningShares { delegator } => (PoolKind::Joining, delegator),
189
1
            PoolsKey::AutoCompoundingShares { delegator } => (PoolKind::AutoCompounding, delegator),
190
2
            PoolsKey::ManualRewardsShares { delegator } => (PoolKind::ManualRewards, delegator),
191
            PoolsKey::LeavingShares { delegator } => (PoolKind::Leaving, delegator),
192
            _ => continue, // we only care about share values
193
        };
194

            
195
4
        let value = Pools::<T>::get(&candidate, key);
196
4
        // Are 0/Default values automatically removed from storage?
197
4
        // In case they aren't we check the amount of shares isn't 0.
198
4
        if value.is_zero() {
199
1
            continue;
200
3
        }
201
3

            
202
3
        // We first modify the delegator summary. If the summary is empty it means we have not yet
203
3
        // encountered that delegator for this candidate, so we'll consider it a new delegator that
204
3
        // will increase the `delegators` count in the candidate summary.
205
3
        let mut new_delegator = false;
206
3
        meter.consume(T::DbWeight::get().reads_writes(2, 2));
207
3

            
208
3
        D::mutate(&delegator, &candidate, |summary| {
209
3
            if summary.is_empty() {
210
3
                new_delegator = true;
211
3
            }
212

            
213
3
            summary.set_pool(pool, true);
214
3
        });
215
3

            
216
3
        C::mutate(&candidate, |summary| {
217
3
            if new_delegator {
218
3
                summary.delegators.saturating_inc();
219
3
            }
220

            
221
3
            summary.pool_delegators_mut(pool).saturating_inc();
222
3
        });
223
    }
224

            
225
1
    log::info!(target: LOG_TARGET, "Migration finished. Processed {count} delegations");
226
1
    PausePoolsExtrinsics::<T>::put(false);
227
1

            
228
1
    Ok(None)
229
1
}