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 fp_evm::MAX_TRANSACTION_GAS_LIMIT;
22
use frame_support::assert_ok;
23
use nimbus_primitives::NimbusId;
24
use pallet_author_mapping::{keys_wrapper, Call as AuthorMappingCall, Event as AuthorMappingEvent};
25
use pallet_balances::Event as BalancesEvent;
26
use pallet_evm::{Call as EvmCall, Event as EvmEvent};
27
use precompile_utils::{prelude::*, testing::*};
28
use sp_core::crypto::UncheckedFrom;
29
use sp_core::{H160, H256, U256};
30
use sp_runtime::traits::Dispatchable;
31

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
269
1
			let input = PCall::remove_keys {}.into();
270

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

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

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

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

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

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

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

            
369
mod nimbus_id_of {
370
	use super::*;
371

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

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

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

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

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

            
411
mod address_of {
412
	use super::*;
413

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

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

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

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

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

            
447
mod keys_of {
448
	use super::*;
449

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

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

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

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

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

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

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