Skip to main content

pallet_transporter/
migrations.rs

1//! Migrations for pallet-transporter.
2
3use crate::{Config, Pallet};
4use core::marker::PhantomData;
5use frame_support::migrations::VersionedMigration;
6use frame_support::traits::UncheckedOnRuntimeUpgrade;
7use frame_support::weights::Weight;
8
9/// Seeds `AllDomainsSupply` from the existing component storages on the introducing
10/// runtime upgrade (storage version 0 -> 1).
11pub type VersionCheckedMigrateTransporterV0ToV1<T> = VersionedMigration<
12    0,
13    1,
14    VersionUncheckedMigrateV0ToV1<T>,
15    Pallet<T>,
16    <T as frame_system::Config>::DbWeight,
17>;
18
19pub struct VersionUncheckedMigrateV0ToV1<T>(PhantomData<T>);
20impl<T: Config> UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateV0ToV1<T> {
21    fn on_runtime_upgrade() -> Weight {
22        seed_all_domains_supply::seed::<T>()
23    }
24}
25
26mod seed_all_domains_supply {
27    use crate::{
28        AllDomainsSupply, BalanceOf, CancelledTransfers, Config, DomainBalances,
29        UnconfirmedTransfers,
30    };
31    use frame_support::traits::{Defensive, Get};
32    use frame_support::weights::Weight;
33    use sp_runtime::traits::{CheckedAdd, Zero};
34
35    pub(super) fn seed<T: Config>() -> Weight {
36        // AllDomainsSupply is only read on the consensus runtime; on domains the aggregate is
37        // unused, so skip the seed and let the versioned migration only bump the storage version.
38        if !T::SelfChainId::get().is_consensus_chain() {
39            return Weight::zero();
40        }
41
42        let mut reads: u64 = 0;
43        let mut total = BalanceOf::<T>::zero();
44        for value in DomainBalances::<T>::iter_values() {
45            reads += 1;
46            total = total.checked_add(&value).defensive_unwrap_or(total);
47        }
48        for value in UnconfirmedTransfers::<T>::iter_values() {
49            reads += 1;
50            total = total.checked_add(&value).defensive_unwrap_or(total);
51        }
52        for value in CancelledTransfers::<T>::iter_values() {
53            reads += 1;
54            total = total.checked_add(&value).defensive_unwrap_or(total);
55        }
56
57        AllDomainsSupply::<T>::put(total);
58
59        // Fixed base plus one read per component-storage entry and the single aggregate write.
60        let base_weight = T::DbWeight::get().reads_writes(1, 1);
61        base_weight.saturating_add(T::DbWeight::get().reads(reads))
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::VersionCheckedMigrateTransporterV0ToV1;
68    use super::seed_all_domains_supply::seed;
69    use crate::mock::consensus::{ConsensusMockRuntime, new_test_ext as new_consensus_test_ext};
70    use crate::mock::{MockRuntime, assert_all_domains_supply_reconciles, new_test_ext};
71    use crate::{
72        AllDomainsSupply, CancelledTransfers, DomainBalances, Pallet, UnconfirmedTransfers,
73    };
74    use frame_support::traits::{GetStorageVersion, OnRuntimeUpgrade, StorageVersion};
75    use sp_domains::DomainId;
76    use sp_messenger::messages::ChainId;
77
78    #[test]
79    fn seed_migration_sets_aggregate_from_existing_storage() {
80        new_consensus_test_ext().execute_with(|| {
81            let domain = DomainId::new(0);
82            DomainBalances::<ConsensusMockRuntime>::insert(domain, 500u64);
83            UnconfirmedTransfers::<ConsensusMockRuntime>::insert(
84                ChainId::Consensus,
85                ChainId::Domain(domain),
86                70u64,
87            );
88            CancelledTransfers::<ConsensusMockRuntime>::insert(
89                ChainId::Domain(domain),
90                ChainId::Consensus,
91                5u64,
92            );
93            // aggregate deliberately left stale at 0 before the migration runs
94            assert_eq!(AllDomainsSupply::<ConsensusMockRuntime>::get(), 0);
95
96            seed::<ConsensusMockRuntime>();
97
98            assert_eq!(Pallet::<ConsensusMockRuntime>::all_domains_supply(), 575);
99            assert_all_domains_supply_reconciles::<ConsensusMockRuntime>();
100        });
101    }
102
103    #[test]
104    fn seed_migration_sums_multiple_entries_across_maps() {
105        new_consensus_test_ext().execute_with(|| {
106            DomainBalances::<ConsensusMockRuntime>::insert(DomainId::new(0), 100u64);
107            DomainBalances::<ConsensusMockRuntime>::insert(DomainId::new(1), 200u64);
108            DomainBalances::<ConsensusMockRuntime>::insert(DomainId::new(2), 300u64);
109            UnconfirmedTransfers::<ConsensusMockRuntime>::insert(
110                ChainId::Consensus,
111                ChainId::Domain(DomainId::new(0)),
112                10u64,
113            );
114            UnconfirmedTransfers::<ConsensusMockRuntime>::insert(
115                ChainId::Consensus,
116                ChainId::Domain(DomainId::new(1)),
117                20u64,
118            );
119            CancelledTransfers::<ConsensusMockRuntime>::insert(
120                ChainId::Domain(DomainId::new(0)),
121                ChainId::Consensus,
122                1u64,
123            );
124            CancelledTransfers::<ConsensusMockRuntime>::insert(
125                ChainId::Domain(DomainId::new(1)),
126                ChainId::Consensus,
127                2u64,
128            );
129
130            seed::<ConsensusMockRuntime>();
131
132            assert_eq!(Pallet::<ConsensusMockRuntime>::all_domains_supply(), 633);
133            assert_all_domains_supply_reconciles::<ConsensusMockRuntime>();
134        });
135    }
136
137    #[test]
138    fn seed_migration_with_empty_maps_is_zero() {
139        new_consensus_test_ext().execute_with(|| {
140            seed::<ConsensusMockRuntime>();
141            assert_eq!(Pallet::<ConsensusMockRuntime>::all_domains_supply(), 0);
142            assert_all_domains_supply_reconciles::<ConsensusMockRuntime>();
143        });
144    }
145
146    #[test]
147    fn seed_migration_is_noop_on_domains() {
148        new_test_ext().execute_with(|| {
149            // On a domain the aggregate is unused, so the seed must skip even with populated maps.
150            DomainBalances::<MockRuntime>::insert(DomainId::new(0), 500u64);
151            UnconfirmedTransfers::<MockRuntime>::insert(
152                ChainId::Consensus,
153                ChainId::Domain(DomainId::new(0)),
154                70u64,
155            );
156
157            seed::<MockRuntime>();
158
159            assert_eq!(Pallet::<MockRuntime>::all_domains_supply(), 0);
160        });
161    }
162
163    #[test]
164    fn versioned_migration_seeds_once_and_is_idempotent() {
165        new_consensus_test_ext().execute_with(|| {
166            // start at on-chain version 0 (genesis otherwise sets it to 1)
167            StorageVersion::new(0).put::<Pallet<ConsensusMockRuntime>>();
168            DomainBalances::<ConsensusMockRuntime>::insert(DomainId::new(0), 500u64);
169            UnconfirmedTransfers::<ConsensusMockRuntime>::insert(
170                ChainId::Consensus,
171                ChainId::Domain(DomainId::new(0)),
172                70u64,
173            );
174            CancelledTransfers::<ConsensusMockRuntime>::insert(
175                ChainId::Domain(DomainId::new(0)),
176                ChainId::Consensus,
177                5u64,
178            );
179
180            <VersionCheckedMigrateTransporterV0ToV1<ConsensusMockRuntime> as OnRuntimeUpgrade>::on_runtime_upgrade();
181            assert_eq!(Pallet::<ConsensusMockRuntime>::all_domains_supply(), 575);
182            assert_eq!(
183                Pallet::<ConsensusMockRuntime>::on_chain_storage_version(),
184                StorageVersion::new(1)
185            );
186
187            // second run is version-gated to a no-op: mutate a map, re-run, aggregate stays put
188            DomainBalances::<ConsensusMockRuntime>::insert(DomainId::new(1), 1_000u64);
189            <VersionCheckedMigrateTransporterV0ToV1<ConsensusMockRuntime> as OnRuntimeUpgrade>::on_runtime_upgrade();
190            assert_eq!(
191                Pallet::<ConsensusMockRuntime>::all_domains_supply(),
192                575,
193                "migration must be idempotent (version-gated)"
194            );
195        });
196    }
197}