1
// Copyright 2019-2025 PureStake Inc.
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
use crate::mock::{
18
	events, AuthorMappingAccount, ExtBuilder, PCall, Precompiles, PrecompilesValue, Runtime,
19
	RuntimeCall, RuntimeOrigin,
20
};
21
use frame_support::assert_ok;
22
use nimbus_primitives::NimbusId;
23
use pallet_author_mapping::{keys_wrapper, Call as AuthorMappingCall, Event as AuthorMappingEvent};
24
use pallet_balances::Event as BalancesEvent;
25
use pallet_evm::{Call as EvmCall, Event as EvmEvent};
26
use precompile_utils::{prelude::*, testing::*};
27
use sp_core::crypto::UncheckedFrom;
28
use sp_core::{H160, H256, U256};
29
use sp_runtime::traits::Dispatchable;
30

            
31
9
fn precompiles() -> Precompiles<Runtime> {
32
9
	PrecompilesValue::get()
33
9
}
34

            
35
5
fn evm_call(input: Vec<u8>) -> EvmCall<Runtime> {
36
5
	EvmCall::call {
37
5
		source: Alice.into(),
38
5
		target: Precompile1.into(),
39
5
		input,
40
5
		value: U256::zero(), // No value sent in EVM
41
5
		gas_limit: u64::max_value(),
42
5
		max_fee_per_gas: 0.into(),
43
5
		max_priority_fee_per_gas: Some(U256::zero()),
44
5
		nonce: None, // Use the next nonce
45
5
		access_list: Vec::new(),
46
5
	}
47
5
}
48

            
49
#[test]
50
1
fn selector_less_than_four_bytes() {
51
1
	ExtBuilder::default().build().execute_with(|| {
52
1
		// This selector is only three bytes long when four are required.
53
1
		precompiles()
54
1
			.prepare_test(Alice, Precompile1, vec![1u8, 2u8, 3u8])
55
1
			.execute_reverts(|output| output == b"Tried to read selector out of bounds");
56
1
	});
57
1
}
58

            
59
#[test]
60
1
fn no_selector_exists_but_length_is_right() {
61
1
	ExtBuilder::default().build().execute_with(|| {
62
1
		precompiles()
63
1
			.prepare_test(Alice, Precompile1, vec![1u8, 2u8, 3u8, 4u8])
64
1
			.execute_reverts(|output| output == b"Unknown selector");
65
1
	});
66
1
}
67

            
68
#[test]
69
1
fn selectors() {
70
1
	assert!(PCall::add_association_selectors().contains(&0xef8b6cd8));
71
1
	assert!(PCall::update_association_selectors().contains(&0x25a39da5));
72
1
	assert!(PCall::clear_association_selectors().contains(&0x448b54d6));
73
1
	assert!(PCall::remove_keys_selectors().contains(&0xa36fee17));
74
1
	assert!(PCall::set_keys_selectors().contains(&0xf1ec919c));
75
1
	assert!(PCall::nimbus_id_of_selectors().contains(&0x3cb194f2));
76
1
	assert!(PCall::address_of_selectors().contains(&0xbb34534c));
77
1
	assert!(PCall::keys_of_selectors().contains(&0x089b7a68));
78
1
}
79

            
80
#[test]
81
1
fn modifiers() {
82
1
	ExtBuilder::default().build().execute_with(|| {
83
1
		let mut tester = PrecompilesModifierTester::new(precompiles(), Alice, Precompile1);
84
1

            
85
1
		tester.test_default_modifier(PCall::add_association_selectors());
86
1
		tester.test_default_modifier(PCall::update_association_selectors());
87
1
		tester.test_default_modifier(PCall::clear_association_selectors());
88
1
		tester.test_default_modifier(PCall::remove_keys_selectors());
89
1
		tester.test_default_modifier(PCall::set_keys_selectors());
90
1
		tester.test_view_modifier(PCall::nimbus_id_of_selectors());
91
1
		tester.test_view_modifier(PCall::address_of_selectors());
92
1
		tester.test_view_modifier(PCall::keys_of_selectors());
93
1
	});
94
1
}
95

            
96
#[test]
97
1
fn add_association_works() {
98
1
	ExtBuilder::default()
99
1
		.with_balances(vec![(Alice.into(), 1000)])
100
1
		.build()
101
1
		.execute_with(|| {
102
1
			let expected_nimbus_id: NimbusId =
103
1
				sp_core::sr25519::Public::unchecked_from([1u8; 32]).into();
104
1

            
105
1
			let input = PCall::add_association {
106
1
				nimbus_id: H256::from([1u8; 32]),
107
1
			}
108
1
			.into();
109
1

            
110
1
			// Make sure the call goes through successfully
111
1
			assert_ok!(RuntimeCall::Evm(evm_call(input)).dispatch(RuntimeOrigin::root()));
112

            
113
			// Assert that the events are as expected
114
1
			assert_eq!(
115
1
				events(),
116
1
				vec![
117
1
					BalancesEvent::Reserved {
118
1
						who: Alice.into(),
119
1
						amount: 10
120
1
					}
121
1
					.into(),
122
1
					AuthorMappingEvent::KeysRegistered {
123
1
						nimbus_id: expected_nimbus_id.clone(),
124
1
						account_id: Alice.into(),
125
1
						keys: expected_nimbus_id.into(),
126
1
					}
127
1
					.into(),
128
1
					EvmEvent::Executed {
129
1
						address: Precompile1.into()
130
1
					}
131
1
					.into(),
132
1
				]
133
1
			);
134
1
		})
135
1
}
136

            
137
#[test]
138
1
fn update_association_works() {
139
1
	ExtBuilder::default()
140
1
		.with_balances(vec![(Alice.into(), 1000)])
141
1
		.build()
142
1
		.execute_with(|| {
143
1
			let first_nimbus_id: NimbusId =
144
1
				sp_core::sr25519::Public::unchecked_from([1u8; 32]).into();
145
1
			let second_nimbus_id: NimbusId =
146
1
				sp_core::sr25519::Public::unchecked_from([2u8; 32]).into();
147
1

            
148
1
			assert_ok!(
149
1
				RuntimeCall::AuthorMapping(AuthorMappingCall::add_association {
150
1
					nimbus_id: first_nimbus_id.clone(),
151
1
				})
152
1
				.dispatch(RuntimeOrigin::signed(Alice.into()))
153
1
			);
154

            
155
1
			let input = PCall::update_association {
156
1
				old_nimbus_id: H256::from([1u8; 32]),
157
1
				new_nimbus_id: H256::from([2u8; 32]),
158
1
			}
159
1
			.into();
160
1

            
161
1
			// Make sure the call goes through successfully
162
1
			assert_ok!(RuntimeCall::Evm(evm_call(input)).dispatch(RuntimeOrigin::root()));
163

            
164
			// Assert that the events are as expected
165
1
			assert_eq!(
166
1
				events(),
167
1
				vec![
168
1
					BalancesEvent::Reserved {
169
1
						who: Alice.into(),
170
1
						amount: 10
171
1
					}
172
1
					.into(),
173
1
					AuthorMappingEvent::KeysRegistered {
174
1
						nimbus_id: first_nimbus_id.clone(),
175
1
						account_id: Alice.into(),
176
1
						keys: first_nimbus_id.into(),
177
1
					}
178
1
					.into(),
179
1
					AuthorMappingEvent::KeysRotated {
180
1
						new_nimbus_id: second_nimbus_id.clone(),
181
1
						account_id: Alice.into(),
182
1
						new_keys: second_nimbus_id.into(),
183
1
					}
184
1
					.into(),
185
1
					EvmEvent::Executed {
186
1
						address: Precompile1.into()
187
1
					}
188
1
					.into(),
189
1
				]
190
1
			);
191
1
		})
192
1
}
193

            
194
#[test]
195
1
fn clear_association_works() {
196
1
	ExtBuilder::default()
197
1
		.with_balances(vec![(Alice.into(), 1000)])
198
1
		.build()
199
1
		.execute_with(|| {
200
1
			let nimbus_id: NimbusId = sp_core::sr25519::Public::unchecked_from([1u8; 32]).into();
201
1

            
202
1
			assert_ok!(
203
1
				RuntimeCall::AuthorMapping(AuthorMappingCall::add_association {
204
1
					nimbus_id: nimbus_id.clone(),
205
1
				})
206
1
				.dispatch(RuntimeOrigin::signed(Alice.into()))
207
1
			);
208

            
209
1
			let input = PCall::clear_association {
210
1
				nimbus_id: H256::from([1u8; 32]),
211
1
			}
212
1
			.into();
213
1

            
214
1
			// Make sure the call goes through successfully
215
1
			assert_ok!(RuntimeCall::Evm(evm_call(input)).dispatch(RuntimeOrigin::root()));
216

            
217
			// Assert that the events are as expected
218
1
			assert_eq!(
219
1
				events(),
220
1
				vec![
221
1
					BalancesEvent::Reserved {
222
1
						who: Alice.into(),
223
1
						amount: 10
224
1
					}
225
1
					.into(),
226
1
					AuthorMappingEvent::KeysRegistered {
227
1
						nimbus_id: nimbus_id.clone(),
228
1
						account_id: Alice.into(),
229
1
						keys: nimbus_id.clone().into(),
230
1
					}
231
1
					.into(),
232
1
					BalancesEvent::Unreserved {
233
1
						who: Alice.into(),
234
1
						amount: 10
235
1
					}
236
1
					.into(),
237
1
					AuthorMappingEvent::KeysRemoved {
238
1
						nimbus_id: nimbus_id.clone(),
239
1
						account_id: Alice.into(),
240
1
						keys: nimbus_id.into(),
241
1
					}
242
1
					.into(),
243
1
					EvmEvent::Executed {
244
1
						address: Precompile1.into()
245
1
					}
246
1
					.into(),
247
1
				]
248
1
			);
249
1
		})
250
1
}
251

            
252
#[test]
253
1
fn remove_keys_works() {
254
1
	ExtBuilder::default()
255
1
		.with_balances(vec![(Alice.into(), 1000)])
256
1
		.build()
257
1
		.execute_with(|| {
258
1
			let nimbus_id: NimbusId = sp_core::sr25519::Public::unchecked_from([1u8; 32]).into();
259
1

            
260
1
			assert_ok!(
261
1
				RuntimeCall::AuthorMapping(AuthorMappingCall::add_association {
262
1
					nimbus_id: nimbus_id.clone(),
263
1
				})
264
1
				.dispatch(RuntimeOrigin::signed(Alice.into()))
265
1
			);
266

            
267
1
			let input = PCall::remove_keys {}.into();
268
1

            
269
1
			// Make sure the call goes through successfully
270
1
			assert_ok!(RuntimeCall::Evm(evm_call(input)).dispatch(RuntimeOrigin::root()));
271

            
272
			// Assert that the events are as expected
273
1
			assert_eq!(
274
1
				events(),
275
1
				vec![
276
1
					BalancesEvent::Reserved {
277
1
						who: Alice.into(),
278
1
						amount: 10
279
1
					}
280
1
					.into(),
281
1
					AuthorMappingEvent::KeysRegistered {
282
1
						nimbus_id: nimbus_id.clone(),
283
1
						account_id: Alice.into(),
284
1
						keys: nimbus_id.clone().into(),
285
1
					}
286
1
					.into(),
287
1
					BalancesEvent::Unreserved {
288
1
						who: Alice.into(),
289
1
						amount: 10
290
1
					}
291
1
					.into(),
292
1
					AuthorMappingEvent::KeysRemoved {
293
1
						nimbus_id: nimbus_id.clone(),
294
1
						account_id: Alice.into(),
295
1
						keys: nimbus_id.into(),
296
1
					}
297
1
					.into(),
298
1
					EvmEvent::Executed {
299
1
						address: Precompile1.into()
300
1
					}
301
1
					.into(),
302
1
				]
303
1
			);
304
1
		})
305
1
}
306

            
307
#[test]
308
1
fn set_keys_works() {
309
1
	ExtBuilder::default()
310
1
		.with_balances(vec![(Alice.into(), 1000)])
311
1
		.build()
312
1
		.execute_with(|| {
313
1
			let first_nimbus_id: NimbusId =
314
1
				sp_core::sr25519::Public::unchecked_from([1u8; 32]).into();
315
1
			let second_nimbus_id: NimbusId =
316
1
				sp_core::sr25519::Public::unchecked_from([2u8; 32]).into();
317
1
			let first_vrf_key: NimbusId =
318
1
				sp_core::sr25519::Public::unchecked_from([3u8; 32]).into();
319
1
			let second_vrf_key: NimbusId =
320
1
				sp_core::sr25519::Public::unchecked_from([4u8; 32]).into();
321
1

            
322
1
			assert_ok!(RuntimeCall::AuthorMapping(AuthorMappingCall::set_keys {
323
1
				keys: keys_wrapper::<Runtime>(first_nimbus_id.clone(), first_vrf_key.clone()),
324
1
			})
325
1
			.dispatch(RuntimeOrigin::signed(Alice.into())));
326

            
327
			// Create input with keys inside a Solidity bytes.
328
1
			let input = PCall::set_keys {
329
1
				keys: solidity::encode_arguments((H256::from([2u8; 32]), H256::from([4u8; 32])))
330
1
					.into(),
331
1
			}
332
1
			.into();
333
1

            
334
1
			// Make sure the call goes through successfully
335
1
			assert_ok!(RuntimeCall::Evm(evm_call(input)).dispatch(RuntimeOrigin::root()));
336

            
337
			// Assert that the events are as expected
338
1
			assert_eq!(
339
1
				events(),
340
1
				vec![
341
1
					BalancesEvent::Reserved {
342
1
						who: Alice.into(),
343
1
						amount: 10
344
1
					}
345
1
					.into(),
346
1
					AuthorMappingEvent::KeysRegistered {
347
1
						nimbus_id: first_nimbus_id.clone(),
348
1
						account_id: Alice.into(),
349
1
						keys: first_vrf_key.into(),
350
1
					}
351
1
					.into(),
352
1
					AuthorMappingEvent::KeysRotated {
353
1
						new_nimbus_id: second_nimbus_id.clone(),
354
1
						account_id: Alice.into(),
355
1
						new_keys: second_vrf_key.into(),
356
1
					}
357
1
					.into(),
358
1
					EvmEvent::Executed {
359
1
						address: Precompile1.into()
360
1
					}
361
1
					.into(),
362
1
				]
363
1
			);
364
1
		})
365
1
}
366

            
367
mod nimbus_id_of {
368
	use super::*;
369

            
370
2
	fn call(address: impl Into<H160>, expected: H256) {
371
2
		let address = address.into();
372
2
		ExtBuilder::default()
373
2
			.with_balances(vec![(Alice.into(), 1000)])
374
2
			.build()
375
2
			.execute_with(|| {
376
2
				let first_nimbus_id: NimbusId =
377
2
					sp_core::sr25519::Public::unchecked_from([1u8; 32]).into();
378
2
				let first_vrf_key: NimbusId =
379
2
					sp_core::sr25519::Public::unchecked_from([3u8; 32]).into();
380
2

            
381
2
				let call = RuntimeCall::AuthorMapping(AuthorMappingCall::set_keys {
382
2
					keys: keys_wrapper::<Runtime>(first_nimbus_id.clone(), first_vrf_key.clone()),
383
2
				});
384
2
				assert_ok!(call.dispatch(RuntimeOrigin::signed(Alice.into())));
385

            
386
2
				precompiles()
387
2
					.prepare_test(
388
2
						Bob,
389
2
						AuthorMappingAccount,
390
2
						PCall::nimbus_id_of {
391
2
							address: Address(address),
392
2
						},
393
2
					)
394
2
					.execute_returns(expected);
395
2
			})
396
2
	}
397

            
398
	#[test]
399
1
	fn known_address() {
400
1
		call(Alice, H256::from([1u8; 32]));
401
1
	}
402

            
403
	#[test]
404
1
	fn unknown_address() {
405
1
		call(Bob, H256::from([0u8; 32]));
406
1
	}
407
}
408

            
409
mod address_of {
410
	use super::*;
411

            
412
2
	fn call(nimbus_id: H256, expected: impl Into<H160>) {
413
2
		let expected = expected.into();
414
2
		ExtBuilder::default()
415
2
			.with_balances(vec![(Alice.into(), 1000)])
416
2
			.build()
417
2
			.execute_with(|| {
418
2
				let first_nimbus_id: NimbusId =
419
2
					sp_core::sr25519::Public::unchecked_from([1u8; 32]).into();
420
2
				let first_vrf_key: NimbusId =
421
2
					sp_core::sr25519::Public::unchecked_from([3u8; 32]).into();
422
2

            
423
2
				let call = RuntimeCall::AuthorMapping(AuthorMappingCall::set_keys {
424
2
					keys: keys_wrapper::<Runtime>(first_nimbus_id.clone(), first_vrf_key.clone()),
425
2
				});
426
2
				assert_ok!(call.dispatch(RuntimeOrigin::signed(Alice.into())));
427

            
428
2
				precompiles()
429
2
					.prepare_test(Bob, AuthorMappingAccount, PCall::address_of { nimbus_id })
430
2
					.execute_returns(Address(expected));
431
2
			})
432
2
	}
433

            
434
	#[test]
435
1
	fn known_id() {
436
1
		call(H256::from([1u8; 32]), Alice);
437
1
	}
438

            
439
	#[test]
440
1
	fn unknown_id() {
441
1
		call(H256::from([42u8; 32]), Address(H160::zero()));
442
1
	}
443
}
444

            
445
mod keys_of {
446
	use super::*;
447

            
448
2
	fn call(nimbus_id: H256, expected: Vec<u8>) {
449
2
		let expected: UnboundedBytes = expected.into();
450
2
		ExtBuilder::default()
451
2
			.with_balances(vec![(Alice.into(), 1000)])
452
2
			.build()
453
2
			.execute_with(|| {
454
2
				let first_nimbus_id: NimbusId =
455
2
					sp_core::sr25519::Public::unchecked_from([1u8; 32]).into();
456
2
				let first_vrf_key: NimbusId =
457
2
					sp_core::sr25519::Public::unchecked_from([3u8; 32]).into();
458
2

            
459
2
				let call = RuntimeCall::AuthorMapping(AuthorMappingCall::set_keys {
460
2
					keys: keys_wrapper::<Runtime>(first_nimbus_id.clone(), first_vrf_key.clone()),
461
2
				});
462
2
				assert_ok!(call.dispatch(RuntimeOrigin::signed(Alice.into())));
463

            
464
2
				precompiles()
465
2
					.prepare_test(Bob, AuthorMappingAccount, PCall::keys_of { nimbus_id })
466
2
					.execute_returns(expected);
467
2
			})
468
2
	}
469

            
470
	#[test]
471
1
	fn known_id() {
472
1
		call(H256::from([1u8; 32]), vec![3u8; 32]);
473
1
	}
474

            
475
	#[test]
476
1
	fn unknown_id() {
477
1
		call(H256::from([42u8; 32]), Vec::new());
478
1
	}
479
}
480

            
481
#[test]
482
1
fn test_solidity_interface_has_all_function_selectors_documented_and_implemented() {
483
1
	check_precompile_implements_solidity_interfaces(
484
1
		&["AuthorMappingInterface.sol"],
485
1
		PCall::supports_selector,
486
1
	)
487
1
}
488

            
489
#[test]
490
1
fn test_deprecated_solidity_selectors_are_supported() {
491
5
	for deprecated_function in [
492
		"add_association(bytes32)",
493
1
		"update_association(bytes32,bytes32)",
494
1
		"clear_association(bytes32)",
495
1
		"remove_keys()",
496
1
		"set_keys(bytes)",
497
	] {
498
5
		let selector = compute_selector(deprecated_function);
499
5
		if !PCall::supports_selector(selector) {
500
			panic!(
501
				"failed decoding selector 0x{:x} => '{}' as Action",
502
				selector, deprecated_function,
503
			)
504
5
		}
505
	}
506
1
}