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
		authorization_list: Vec::new(),
47
5
	}
48
5
}
49

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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