1
// Copyright 2024 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
//! Unit testing
18
use {
19
	crate::{
20
		mock::{ExtBuilder, LazyMigrations, RuntimeOrigin, Test},
21
		Error, StateMigrationStatus, StateMigrationStatusValue, MAX_ITEM_PROOF_SIZE,
22
		PROOF_SIZE_BUFFER,
23
	},
24
	frame_support::{assert_noop, assert_ok, traits::Hooks, weights::Weight},
25
	rlp::RlpStream,
26
	sp_core::{H160, H256},
27
	sp_io::hashing::keccak_256,
28
	sp_runtime::{traits::Bounded, AccountId32},
29
};
30

            
31
use pallet_evm::AddressMapping;
32

            
33
// Helper function that calculates the contract address
34
100
pub fn contract_address(sender: H160, nonce: u64) -> H160 {
35
100
	let mut rlp = RlpStream::new_list(2);
36
100
	rlp.append(&sender);
37
100
	rlp.append(&nonce);
38
100

            
39
100
	H160::from_slice(&keccak_256(&rlp.out())[12..])
40
100
}
41

            
42
102
fn address_build(seed: u8) -> H160 {
43
102
	let address = H160::from(H256::from(keccak_256(&[seed; 32])));
44
102
	address
45
102
}
46

            
47
// Helper function that creates a `num_entries` storage entries for a contract
48
100
fn mock_contract_with_entries(seed: u8, nonce: u64, num_entries: u32) -> H160 {
49
100
	let address = address_build(seed);
50
100

            
51
100
	let contract_address = contract_address(address, nonce);
52
100
	let account_id =
53
100
		<Test as pallet_evm::Config>::AddressMapping::into_account_id(contract_address);
54
100
	let _ = frame_system::Pallet::<Test>::inc_sufficients(&account_id);
55

            
56
	// Add num_entries storage entries to the suicided contract
57
10000
	for i in 0..num_entries {
58
10000
		pallet_evm::AccountStorages::<Test>::insert(
59
10000
			contract_address,
60
10000
			H256::from_low_u64_be(i as u64),
61
10000
			H256::from_low_u64_be(i as u64),
62
10000
		);
63
10000
	}
64

            
65
100
	contract_address
66
100
}
67

            
68
1
fn create_dummy_contract_without_metadata(seed: u8) -> H160 {
69
1
	let address = address_build(seed);
70
1
	let dummy_code = vec![1, 2, 3];
71
1
	pallet_evm::AccountCodes::<Test>::insert(address, dummy_code);
72
1
	address
73
1
}
74

            
75
#[test]
76
1
fn test_create_contract_metadata_contract_not_exist() {
77
1
	ExtBuilder::default().build().execute_with(|| {
78
1
		assert_noop!(
79
1
			LazyMigrations::create_contract_metadata(
80
1
				RuntimeOrigin::signed(AccountId32::from([45; 32])),
81
1
				address_build(1),
82
1
			),
83
1
			Error::<Test>::ContractNotExist
84
1
		);
85
1
	});
86
1
}
87

            
88
#[test]
89
1
fn test_create_contract_metadata_success_path() {
90
1
	ExtBuilder::default().build().execute_with(|| {
91
1
		// Setup: create a dummy contract
92
1
		let address = create_dummy_contract_without_metadata(1);
93
1

            
94
1
		assert_ok!(LazyMigrations::create_contract_metadata(
95
1
			RuntimeOrigin::signed(AccountId32::from([45; 32])),
96
1
			address,
97
1
		));
98

            
99
1
		assert!(pallet_evm::AccountCodesMetadata::<Test>::get(address).is_some());
100

            
101
		// Should not be able to set metadata again
102
1
		assert_noop!(
103
1
			LazyMigrations::create_contract_metadata(
104
1
				RuntimeOrigin::signed(AccountId32::from([45; 32])),
105
1
				address,
106
1
			),
107
1
			Error::<Test>::ContractMetadataAlreadySet
108
1
		);
109
1
	});
110
1
}
111

            
112
4
fn count_keys_and_data_without_code() -> (u64, u64) {
113
4
	let mut keys: u64 = 0;
114
4
	let mut data: u64 = 0;
115
4

            
116
4
	let mut current_key: Option<Vec<u8>> = Some(Default::default());
117
10148
	while let Some(key) = current_key {
118
10144
		if key.as_slice() == sp_core::storage::well_known_keys::CODE {
119
4
			current_key = sp_io::storage::next_key(&key);
120
4
			continue;
121
10140
		}
122
10140
		keys += 1;
123
10140
		if let Some(_) = sp_io::storage::get(&key) {
124
10136
			data += 1;
125
10136
		}
126
10140
		current_key = sp_io::storage::next_key(&key);
127
	}
128

            
129
4
	(keys, data)
130
4
}
131

            
132
210
fn weight_for(read: u64, write: u64) -> Weight {
133
210
	<Test as frame_system::Config>::DbWeight::get().reads_writes(read, write)
134
210
}
135

            
136
206
fn rem_weight_for_entries(num_entries: u64) -> Weight {
137
206
	let proof = PROOF_SIZE_BUFFER + num_entries * MAX_ITEM_PROOF_SIZE;
138
206
	Weight::from_parts(u64::max_value(), proof)
139
206
}
140

            
141
#[test]
142
1
fn test_state_migration_baseline() {
143
1
	ExtBuilder::default().build().execute_with(|| {
144
1
		assert_eq!(
145
1
			StateMigrationStatusValue::<Test>::get(),
146
1
			(StateMigrationStatus::NotStarted, 0)
147
1
		);
148

            
149
1
		let (keys, data) = count_keys_and_data_without_code();
150
1
		println!("Keys: {}, Data: {}", keys, data);
151
1

            
152
1
		let weight = LazyMigrations::on_idle(0, Weight::max_value());
153
1

            
154
1
		// READS: 2 * keys + 2 (skipped and status)
155
1
		// Next key requests = keys (we have first key as default which is not counted, and extra
156
1
		// next_key request to check if we are done)
157
1
		//
158
1
		// 1 next key request for the skipped key ":code"
159
1
		// Read requests = keys (we read each key once)
160
1
		// 1 Read request for the StateMigrationStatusValue
161
1

            
162
1
		// WRITES: data + 1 (status)
163
1
		// Write requests = data (we write each data once)
164
1
		// 1 Write request for the StateMigrationStatusValue
165
1
		assert_eq!(weight, weight_for(2 * keys + 2, data + 1));
166

            
167
1
		assert_eq!(
168
1
			StateMigrationStatusValue::<Test>::get(),
169
1
			(StateMigrationStatus::Complete, keys)
170
1
		);
171
1
	})
172
1
}
173

            
174
#[test]
175
1
fn test_state_migration_cannot_fit_any_item() {
176
1
	ExtBuilder::default().build().execute_with(|| {
177
1
		StateMigrationStatusValue::<Test>::put((StateMigrationStatus::NotStarted, 0));
178
1

            
179
1
		let weight = LazyMigrations::on_idle(0, rem_weight_for_entries(0));
180
1

            
181
1
		assert_eq!(weight, weight_for(0, 0));
182
1
	})
183
1
}
184

            
185
#[test]
186
1
fn test_state_migration_when_complete() {
187
1
	ExtBuilder::default().build().execute_with(|| {
188
1
		StateMigrationStatusValue::<Test>::put((StateMigrationStatus::Complete, 0));
189
1

            
190
1
		let weight = LazyMigrations::on_idle(0, Weight::max_value());
191
1

            
192
1
		// just reading the status of the migration
193
1
		assert_eq!(weight, weight_for(1, 0));
194
1
	})
195
1
}
196

            
197
#[test]
198
1
fn test_state_migration_when_errored() {
199
1
	ExtBuilder::default().build().execute_with(|| {
200
1
		StateMigrationStatusValue::<Test>::put((
201
1
			StateMigrationStatus::Error("Error".as_bytes().to_vec().try_into().unwrap_or_default()),
202
1
			1,
203
1
		));
204
1

            
205
1
		let weight = LazyMigrations::on_idle(0, Weight::max_value());
206
1

            
207
1
		// just reading the status of the migration
208
1
		assert_eq!(weight, weight_for(1, 0));
209
1
	})
210
1
}
211

            
212
#[test]
213
1
fn test_state_migration_can_only_fit_one_item() {
214
1
	ExtBuilder::default().build().execute_with(|| {
215
1
		assert_eq!(
216
1
			StateMigrationStatusValue::<Test>::get(),
217
1
			(StateMigrationStatus::NotStarted, 0)
218
1
		);
219

            
220
1
		let data = sp_io::storage::get(Default::default());
221
1
		let weight = LazyMigrations::on_idle(0, rem_weight_for_entries(1));
222
1

            
223
1
		let reads = 2; // key read + status read
224
1
		let writes = 1 + data.map(|_| 1).unwrap_or(0);
225
1
		assert_eq!(weight, weight_for(reads, writes));
226

            
227
1
		assert!(matches!(
228
1
			StateMigrationStatusValue::<Test>::get(),
229
			(StateMigrationStatus::Started(_), 1)
230
		));
231

            
232
1
		let weight = LazyMigrations::on_idle(0, rem_weight_for_entries(3));
233
1
		let reads = 3 + 3 + 1; // next key + key read + status
234
1
		let writes = 1 + 3; // status write + key write
235
1
		assert_eq!(weight, weight_for(reads, writes));
236
1
	})
237
1
}
238

            
239
#[test]
240
1
fn test_state_migration_can_only_fit_three_item() {
241
1
	ExtBuilder::default().build().execute_with(|| {
242
1
		assert_eq!(
243
1
			StateMigrationStatusValue::<Test>::get(),
244
1
			(StateMigrationStatus::NotStarted, 0)
245
1
		);
246

            
247
1
		let weight = LazyMigrations::on_idle(0, rem_weight_for_entries(3));
248
1

            
249
1
		// 2 next key requests (default key dons't need a next key request) + 1 status read
250
1
		// 3 key reads.
251
1
		// 1 status write + 2 key writes (default key doesn't have any data)
252
1
		let reads = 6;
253
1
		let writes = 3;
254
1
		assert_eq!(weight, weight_for(reads, writes));
255

            
256
1
		assert!(matches!(
257
1
			StateMigrationStatusValue::<Test>::get(),
258
			(StateMigrationStatus::Started(_), 3)
259
		));
260
1
	})
261
1
}
262

            
263
#[test]
264
1
fn test_state_migration_can_fit_exactly_all_item() {
265
1
	ExtBuilder::default().build().execute_with(|| {
266
1
		assert_eq!(
267
1
			StateMigrationStatusValue::<Test>::get(),
268
1
			(StateMigrationStatus::NotStarted, 0)
269
1
		);
270

            
271
1
		let (keys, data) = count_keys_and_data_without_code();
272
1
		let weight = LazyMigrations::on_idle(0, rem_weight_for_entries(keys));
273
1

            
274
1
		// we deduct the extra next_key request to check if we are done.
275
1
		// will know if we are done on the next call to on_idle
276
1
		assert_eq!(weight, weight_for(2 * keys + 1, data + 1));
277

            
278
1
		assert!(matches!(
279
1
			StateMigrationStatusValue::<Test>::get(),
280
1
			(StateMigrationStatus::Started(_), n) if n == keys,
281
		));
282

            
283
		// after calling on_idle status is added to the storage so we need to account for that
284
1
		let (new_keys, new_data) = count_keys_and_data_without_code();
285
1
		let (diff_keys, diff_data) = (new_keys - keys, new_data - data);
286
1

            
287
1
		let weight = LazyMigrations::on_idle(0, rem_weight_for_entries(1 + diff_keys));
288
1
		// (next_key + read) for each new key + status + next_key to check if we are done
289
1
		let reads = diff_keys * 2 + 2;
290
1
		let writes = 1 + diff_data; // status
291
1
		assert_eq!(weight, weight_for(reads, writes));
292

            
293
1
		assert!(matches!(
294
1
			StateMigrationStatusValue::<Test>::get(),
295
1
			(StateMigrationStatus::Complete, n) if n == new_keys,
296
		));
297
1
	})
298
1
}
299

            
300
#[test]
301
1
fn test_state_migration_will_migrate_10_000_items() {
302
1
	ExtBuilder::default().build().execute_with(|| {
303
1
		assert_eq!(
304
1
			StateMigrationStatusValue::<Test>::get(),
305
1
			(StateMigrationStatus::NotStarted, 0)
306
1
		);
307

            
308
101
		for i in 0..100 {
309
100
			mock_contract_with_entries(i as u8, i as u64, 100);
310
100
		}
311

            
312
1
		StateMigrationStatusValue::<Test>::put((StateMigrationStatus::NotStarted, 0));
313
1

            
314
1
		let (keys, data) = count_keys_and_data_without_code();
315
1

            
316
1
		// assuming we can only fit 100 items at a time
317
1

            
318
1
		let mut total_weight: Weight = Weight::zero();
319
1
		let num_of_on_idle_calls = 200;
320
1
		let entries_per_on_idle = 100;
321
1
		let needed_on_idle_calls = (keys as f64 / entries_per_on_idle as f64).ceil() as u64;
322
1

            
323
1
		// Reads:
324
1
		// Read status => num_of_on_idle_calls
325
1
		// Read keys   => keys
326
1
		// Next keys   => keys - 1  + 1 skip + 1 done check
327
1
		//
328
1
		// Writes:
329
1
		// Write status => needed_on_idle_calls
330
1
		// Write keys   => data
331
1
		let expected_reads = (keys - 1 + 2) + keys + num_of_on_idle_calls;
332
1
		let expected_writes = data + needed_on_idle_calls;
333
1

            
334
1
		println!("Keys: {}, Data: {}", keys, data);
335
1
		println!("entries_per_on_idle: {}", entries_per_on_idle);
336
1
		println!("num_of_on_idle_calls: {}", num_of_on_idle_calls);
337
1
		println!("needed_on_idle_calls: {}", needed_on_idle_calls);
338
1
		println!(
339
1
			"Expected Reads: {}, Expected Writes: {}",
340
1
			expected_reads, expected_writes
341
1
		);
342

            
343
200
		for i in 1..=num_of_on_idle_calls {
344
200
			let weight = LazyMigrations::on_idle(i, rem_weight_for_entries(entries_per_on_idle));
345
200
			total_weight = total_weight.saturating_add(weight);
346
200

            
347
200
			let status = StateMigrationStatusValue::<Test>::get();
348
200
			if i < needed_on_idle_calls {
349
101
				let migrated_so_far = i * entries_per_on_idle;
350
101
				assert!(
351
101
					matches!(status, (StateMigrationStatus::Started(_), n) if n == migrated_so_far),
352
					"Status: {:?} at call: #{} doesn't match Started",
353
					status,
354
					i,
355
				);
356
101
				assert!(weight.all_gte(weight_for(1, 0)));
357
			} else {
358
99
				assert!(
359
99
					matches!(status, (StateMigrationStatus::Complete, n) if n == keys),
360
					"Status: {:?} at call: {} doesn't match Complete",
361
					status,
362
					i,
363
				);
364
99
				if i == needed_on_idle_calls {
365
					// last call to on_idle
366
1
					assert!(weight.all_gte(weight_for(1, 0)));
367
				} else {
368
					// extra calls to on_idle, just status update check
369
98
					assert_eq!(weight, weight_for(1, 0));
370
				}
371
			}
372
		}
373

            
374
1
		assert_eq!(total_weight, weight_for(expected_reads, expected_writes));
375
1
	})
376
1
}