1
// Copyright 2025 Moonbeam Foundation.
2
// This file is part of Moonbeam.
3

            
4
// Moonbeam 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
// Moonbeam 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 Moonbeam.  If not, see <http://www.gnu.org/licenses/>.
16

            
17
//! Test utilities for crowdloan-rewards pallet
18

            
19
use crate::{self as pallet_crowdloan_rewards, Config};
20
use frame_support::{
21
	construct_runtime, parameter_types,
22
	traits::{Everything, OnFinalize, OnInitialize},
23
	weights::{constants::RocksDbWeight, Weight},
24
	PalletId,
25
};
26
use sp_core::{crypto::AccountId32, ed25519, Pair, H256};
27
use sp_runtime::{
28
	traits::{BlakeTwo256, BlockNumberProvider, IdentityLookup},
29
	BuildStorage, Perbill,
30
};
31

            
32
pub type AccountId = AccountId32;
33
pub type Balance = u128;
34

            
35
// Configure a mock runtime to test the pallet.
36
604
construct_runtime!(
37
604
	pub enum Test
38
604
	{
39
604
		System: frame_system,
40
604
		Balances: pallet_balances,
41
604
		CrowdloanRewards: pallet_crowdloan_rewards,
42
604
		Utility: pallet_utility,
43
604
	}
44
604
);
45

            
46
parameter_types! {
47
	pub const BlockHashCount: u32 = 250;
48
	pub const MaximumBlockWeight: Weight = Weight::from_parts(1024, 1);
49
	pub const MaximumBlockLength: u32 = 2 * 1024;
50
	pub const SS58Prefix: u8 = 42;
51
}
52

            
53
impl frame_system::Config for Test {
54
	type BaseCallFilter = Everything;
55
	type DbWeight = RocksDbWeight;
56
	type RuntimeOrigin = RuntimeOrigin;
57
	type RuntimeTask = RuntimeTask;
58
	type Nonce = u64;
59
	type Block = frame_system::mocking::MockBlockU32<Test>;
60
	type RuntimeCall = RuntimeCall;
61
	type Hash = H256;
62
	type Hashing = BlakeTwo256;
63
	type AccountId = AccountId;
64
	type Lookup = IdentityLookup<Self::AccountId>;
65
	type RuntimeEvent = RuntimeEvent;
66
	type BlockHashCount = BlockHashCount;
67
	type Version = ();
68
	type PalletInfo = PalletInfo;
69
	type AccountData = pallet_balances::AccountData<Balance>;
70
	type OnNewAccount = ();
71
	type OnKilledAccount = ();
72
	type SystemWeightInfo = ();
73
	type BlockWeights = ();
74
	type BlockLength = ();
75
	type SS58Prefix = SS58Prefix;
76
	type OnSetCode = ();
77
	type MaxConsumers = frame_support::traits::ConstU32<16>;
78
	type SingleBlockMigrations = ();
79
	type MultiBlockMigrator = ();
80
	type PreInherents = ();
81
	type PostInherents = ();
82
	type PostTransactions = ();
83
	type ExtensionsWeightInfo = ();
84
}
85

            
86
parameter_types! {
87
	pub const ExistentialDeposit: u128 = 1;
88
}
89

            
90
impl pallet_balances::Config for Test {
91
	type MaxReserves = ();
92
	type ReserveIdentifier = [u8; 4];
93
	type MaxLocks = ();
94
	type Balance = Balance;
95
	type RuntimeEvent = RuntimeEvent;
96
	type DustRemoval = ();
97
	type ExistentialDeposit = ExistentialDeposit;
98
	type AccountStore = System;
99
	type WeightInfo = ();
100
	type RuntimeHoldReason = ();
101
	type FreezeIdentifier = ();
102
	type MaxFreezes = ();
103
	type RuntimeFreezeReason = ();
104
	type DoneSlashHandler = ();
105
}
106

            
107
pub struct MockVestingBlockNumberProvider;
108
impl BlockNumberProvider for MockVestingBlockNumberProvider {
109
	type BlockNumber = u32;
110

            
111
32
	fn current_block_number() -> Self::BlockNumber {
112
32
		System::block_number()
113
32
	}
114

            
115
	#[cfg(feature = "runtime-benchmarks")]
116
	fn set_block_number(n: Self::BlockNumber) {
117
		frame_system::Pallet::<Test>::set_block_number(n);
118
	}
119
}
120

            
121
parameter_types! {
122
	pub const Initialized: bool = false;
123
	pub const InitializationPayment: Perbill = Perbill::from_percent(20);
124
	pub const MaxInitContributors: u32 = 8;
125
	pub const MinimumReward: Balance = 0;
126
	pub const RewardAddressRelayVoteThreshold: Perbill = Perbill::from_percent(60);
127
	pub const SignatureNetworkIdentifier: &'static [u8] = b"TEST_NET";
128
	pub const CrowdloanPalletId: PalletId = PalletId(*b"Crowdloa");
129
}
130

            
131
impl Config for Test {
132
	type RuntimeEvent = RuntimeEvent;
133
	type Initialized = Initialized;
134
	type InitializationPayment = InitializationPayment;
135
	type MaxInitContributors = MaxInitContributors;
136
	type MinimumReward = MinimumReward;
137
	type RewardAddressRelayVoteThreshold = RewardAddressRelayVoteThreshold;
138
	type RewardCurrency = Balances;
139
	type RelayChainAccountId = AccountId;
140
	type RewardAddressChangeOrigin = frame_system::EnsureSigned<AccountId>;
141
	type SignatureNetworkIdentifier = SignatureNetworkIdentifier;
142
	type RewardAddressAssociateOrigin = frame_system::EnsureSigned<AccountId>;
143
	type VestingBlockNumber = u32;
144
	type VestingBlockProvider = MockVestingBlockNumberProvider;
145
	type WeightInfo = ();
146
}
147

            
148
impl pallet_utility::Config for Test {
149
	type RuntimeEvent = RuntimeEvent;
150
	type RuntimeCall = RuntimeCall;
151
	type WeightInfo = ();
152
	type PalletsOrigin = OriginCaller;
153
}
154

            
155
pub(crate) struct ExtBuilder {
156
	funded_accounts: Vec<(AccountId, Option<AccountId>, Balance)>,
157
	init_vesting_block: u32,
158
	end_vesting_block: u32,
159
}
160

            
161
impl Default for ExtBuilder {
162
10
	fn default() -> Self {
163
10
		Self {
164
10
			funded_accounts: vec![
165
10
				// Associated account with rewards
166
10
				(
167
10
					AccountId::from([10u8; 32]),      // relay account
168
10
					Some(AccountId::from([1u8; 32])), // native account
169
10
					10_000u128,                       // reward
170
10
				),
171
10
			],
172
10
			init_vesting_block: 1u32,
173
10
			end_vesting_block: 100u32,
174
10
		}
175
10
	}
176
}
177

            
178
impl ExtBuilder {
179
19
	pub(crate) fn empty() -> sp_io::TestExternalities {
180
19
		// Default for empty genesis (enough for typical manual initialization tests)
181
19
		Self {
182
19
			funded_accounts: vec![],
183
19
			init_vesting_block: 1u32,
184
19
			end_vesting_block: 100u32,
185
19
		}
186
19
		.inner_build(2501)
187
19
	}
188

            
189
5
	pub(crate) fn with_funded_accounts(
190
5
		mut self,
191
5
		funded_accounts: Vec<(AccountId, Option<AccountId>, Balance)>,
192
5
	) -> Self {
193
5
		self.funded_accounts = funded_accounts;
194
5
		self
195
5
	}
196

            
197
10
	pub(crate) fn build(self) -> sp_io::TestExternalities {
198
10
		// Calculate pot size based on genesis rewards
199
10
		let total_rewards: Balance = self
200
10
			.funded_accounts
201
10
			.iter()
202
11
			.map(|(_, _, amount)| *amount)
203
10
			.sum();
204
10

            
205
10
		// Add small dust for funded genesis (dust must be < contributors for validation)
206
10
		self.inner_build(total_rewards + 1)
207
10
	}
208

            
209
29
	fn inner_build(self, pot_size: u128) -> sp_io::TestExternalities {
210
29
		let mut t = frame_system::GenesisConfig::<Test>::default()
211
29
			.build_storage()
212
29
			.unwrap();
213
29

            
214
29
		pallet_balances::GenesisConfig::<Test> {
215
29
			balances: vec![
216
29
				// Pallet account with initial funds
217
29
				(CrowdloanRewards::account_id(), pot_size),
218
29
				// Test accounts
219
29
				(AccountId::from([1u8; 32]), 100_000),
220
29
				(AccountId::from([2u8; 32]), 100_000),
221
29
				(AccountId::from([3u8; 32]), 100_000),
222
29
			],
223
29
			dev_accounts: None,
224
29
		}
225
29
		.assimilate_storage(&mut t)
226
29
		.unwrap();
227
29

            
228
29
		crate::GenesisConfig::<Test> {
229
29
			funded_accounts: self.funded_accounts,
230
29
			init_vesting_block: self.init_vesting_block,
231
29
			end_vesting_block: self.end_vesting_block,
232
29
		}
233
29
		.assimilate_storage(&mut t)
234
29
		.unwrap();
235
29

            
236
29
		t.into()
237
29
	}
238
}
239

            
240
// Helper functions for test setup
241
6
pub fn run_to_block(n: u32) {
242
266
	while System::block_number() < n {
243
260
		CrowdloanRewards::on_finalize(System::block_number());
244
260
		Balances::on_finalize(System::block_number());
245
260
		System::on_finalize(System::block_number());
246
260
		System::set_block_number(System::block_number() + 1);
247
260
		System::on_initialize(System::block_number());
248
260
		Balances::on_initialize(System::block_number());
249
260
		CrowdloanRewards::on_initialize(System::block_number());
250
260
	}
251
6
}
252

            
253
15
pub(crate) fn get_ed25519_pairs(num: u32) -> Vec<ed25519::Pair> {
254
15
	let seed: u128 = 12345678901234567890123456789012;
255
15
	let mut pairs = Vec::new();
256
45
	for i in 0..num {
257
45
		pairs.push(ed25519::Pair::from_seed(
258
45
			(seed.clone() + i as u128)
259
45
				.to_string()
260
45
				.as_bytes()
261
45
				.try_into()
262
45
				.unwrap(),
263
45
		))
264
	}
265
15
	pairs
266
15
}
267

            
268
// Helper function to create a test account
269
228
pub(crate) fn account(id: u8) -> AccountId32 {
270
228
	AccountId32::from([id; 32])
271
228
}
272

            
273
44
pub(crate) fn roll_to(n: u32) {
274
44
	let mut current_block_number = System::block_number();
275
486
	while current_block_number < n {
276
442
		CrowdloanRewards::on_initialize(System::block_number());
277
442
		System::set_block_number(current_block_number);
278
442
		CrowdloanRewards::on_finalize(System::block_number());
279
442
		current_block_number = current_block_number.saturating_add(1);
280
442
	}
281
44
}
282

            
283
8
pub(crate) fn events() -> Vec<super::Event<Test>> {
284
8
	System::events()
285
8
		.into_iter()
286
94
		.map(|r| r.event)
287
94
		.filter_map(|e| {
288
94
			if let RuntimeEvent::CrowdloanRewards(inner) = e {
289
42
				Some(inner)
290
			} else {
291
52
				None
292
			}
293
94
		})
294
8
		.collect::<Vec<_>>()
295
8
}