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::{
18
	assert_event_emitted, hash, log_closed, log_executed, log_proposed, log_voted,
19
	mock::{ExtBuilder, PCall, Precompiles, PrecompilesValue, Runtime, RuntimeOrigin},
20
};
21
use frame_support::{assert_ok, instances::Instance1};
22
use parity_scale_codec::Encode;
23
use precompile_utils::{solidity::codec::Address, testing::*};
24
use sp_core::{H160, H256};
25
use sp_runtime::DispatchError;
26

            
27
33
fn precompiles() -> Precompiles<Runtime> {
28
33
	PrecompilesValue::get()
29
33
}
30

            
31
#[test]
32
1
fn test_solidity_interface_has_all_function_selectors_documented_and_implemented() {
33
1
	check_precompile_implements_solidity_interfaces(&["Collective.sol"], PCall::supports_selector)
34
1
}
35

            
36
#[test]
37
1
fn selector_less_than_four_bytes() {
38
1
	ExtBuilder::default().build().execute_with(|| {
39
1
		// This selector is only three bytes long when four are required.
40
1
		precompiles()
41
1
			.prepare_test(Alice, Precompile1, vec![1u8, 2u8, 3u8])
42
1
			.execute_reverts(|output| output == b"Tried to read selector out of bounds");
43
1
	});
44
1
}
45

            
46
#[test]
47
1
fn no_selector_exists_but_length_is_right() {
48
1
	ExtBuilder::default().build().execute_with(|| {
49
1
		precompiles()
50
1
			.prepare_test(Alice, Precompile1, vec![1u8, 2u8, 3u8, 4u8])
51
1
			.execute_reverts(|output| output == b"Unknown selector");
52
1
	});
53
1
}
54

            
55
#[test]
56
1
fn selectors() {
57
1
	assert!(PCall::execute_selectors().contains(&0x09c5eabe));
58
1
	assert!(PCall::propose_selectors().contains(&0xc57f3260));
59
1
	assert!(PCall::vote_selectors().contains(&0x73e37688));
60
1
	assert!(PCall::close_selectors().contains(&0x638d9d47));
61
1
	assert!(PCall::proposal_hash_selectors().contains(&0xfc379417));
62
1
	assert!(PCall::proposals_selectors().contains(&0x55ef20e6));
63
1
	assert!(PCall::members_selectors().contains(&0xbdd4d18d));
64
1
	assert!(PCall::is_member_selectors().contains(&0xa230c524));
65
1
	assert!(PCall::prime_selectors().contains(&0xc7ee005e));
66
1
}
67

            
68
#[test]
69
1
fn modifiers() {
70
1
	ExtBuilder::default()
71
1
		.with_balances(vec![(Alice.into(), 1000)])
72
1
		.build()
73
1
		.execute_with(|| {
74
1
			let mut tester = PrecompilesModifierTester::new(precompiles(), Alice, Precompile1);
75
1

            
76
1
			tester.test_default_modifier(PCall::execute_selectors());
77
1
			tester.test_default_modifier(PCall::propose_selectors());
78
1
			tester.test_default_modifier(PCall::vote_selectors());
79
1
			tester.test_default_modifier(PCall::close_selectors());
80
1
			tester.test_view_modifier(PCall::proposal_hash_selectors());
81
1
			tester.test_view_modifier(PCall::proposals_selectors());
82
1
			tester.test_view_modifier(PCall::members_selectors());
83
1
			tester.test_view_modifier(PCall::is_member_selectors());
84
1
			tester.test_view_modifier(PCall::prime_selectors());
85
1
		});
86
1
}
87

            
88
#[test]
89
1
fn non_member_cannot_propose() {
90
1
	ExtBuilder::default().build().execute_with(|| {
91
1
		let proposal = pallet_treasury::Call::<Runtime>::spend_local {
92
1
			amount: 1,
93
1
			beneficiary: Alice.into(),
94
1
		};
95
1
		let proposal: <Runtime as frame_system::Config>::RuntimeCall = proposal.into();
96
1
		let proposal = proposal.encode();
97
1

            
98
1
		precompiles()
99
1
			.prepare_test(
100
1
				Alice,
101
1
				Precompile1,
102
1
				PCall::propose {
103
1
					threshold: 1,
104
1
					proposal: proposal.into(),
105
1
				},
106
1
			)
107
1
			.expect_no_logs()
108
1
			.execute_reverts(|output| output.ends_with(b"NotMember\") })"));
109
1
	});
110
1
}
111

            
112
#[test]
113
1
fn non_member_cannot_vote() {
114
1
	ExtBuilder::default().build().execute_with(|| {
115
1
		precompiles()
116
1
			.prepare_test(
117
1
				Alice,
118
1
				Precompile1,
119
1
				PCall::vote {
120
1
					proposal_hash: H256::zero(),
121
1
					proposal_index: 1,
122
1
					approve: false,
123
1
				},
124
1
			)
125
1
			.expect_no_logs()
126
1
			.execute_reverts(|output| output.ends_with(b"NotMember\") })"));
127
1
	});
128
1
}
129

            
130
#[test]
131
1
fn non_member_cannot_execute() {
132
1
	ExtBuilder::default().build().execute_with(|| {
133
1
		let proposal = pallet_treasury::Call::<Runtime>::spend_local {
134
1
			amount: 1,
135
1
			beneficiary: Alice.into(),
136
1
		};
137
1
		let proposal: <Runtime as frame_system::Config>::RuntimeCall = proposal.into();
138
1
		let proposal = proposal.encode();
139
1

            
140
1
		precompiles()
141
1
			.prepare_test(
142
1
				Alice,
143
1
				Precompile1,
144
1
				PCall::execute {
145
1
					proposal: proposal.into(),
146
1
				},
147
1
			)
148
1
			.expect_no_logs()
149
1
			.execute_reverts(|output| output.ends_with(b"NotMember\") })"));
150
1
	});
151
1
}
152

            
153
#[test]
154
1
fn cannot_vote_for_unknown_proposal() {
155
1
	ExtBuilder::default().build().execute_with(|| {
156
1
		precompiles()
157
1
			.prepare_test(
158
1
				Bob,
159
1
				Precompile1,
160
1
				PCall::vote {
161
1
					proposal_hash: H256::zero(),
162
1
					proposal_index: 1,
163
1
					approve: false,
164
1
				},
165
1
			)
166
1
			.expect_no_logs()
167
1
			.execute_reverts(|output| output.ends_with(b"ProposalMissing\") })"));
168
1
	});
169
1
}
170

            
171
#[test]
172
1
fn cannot_close_unknown_proposal() {
173
1
	ExtBuilder::default().build().execute_with(|| {
174
1
		precompiles()
175
1
			.prepare_test(
176
1
				Bob,
177
1
				Precompile1,
178
1
				PCall::close {
179
1
					proposal_hash: H256::zero(),
180
1
					proposal_index: 1,
181
1
					proposal_weight_bound: 0,
182
1
					length_bound: 0,
183
1
				},
184
1
			)
185
1
			.expect_no_logs()
186
1
			.execute_reverts(|output| output.ends_with(b"ProposalMissing\") })"));
187
1
	});
188
1
}
189

            
190
#[test]
191
1
fn member_can_make_instant_proposal() {
192
1
	ExtBuilder::default().build().execute_with(|| {
193
1
		let proposal = pallet_treasury::Call::<Runtime>::spend_local {
194
1
			amount: 1,
195
1
			beneficiary: Alice.into(),
196
1
		};
197
1
		let proposal: <Runtime as frame_system::Config>::RuntimeCall = proposal.into();
198
1
		let proposal = proposal.encode();
199
1
		let proposal_hash: H256 = hash::<Runtime>(&proposal);
200
1

            
201
1
		// Proposal is executed. The proposal call will itself fail but it
202
1
		// still counts as a success according to pallet_collective.
203
1
		precompiles()
204
1
			.prepare_test(
205
1
				Bob,
206
1
				Precompile1,
207
1
				PCall::propose {
208
1
					threshold: 1,
209
1
					proposal: proposal.into(),
210
1
				},
211
1
			)
212
1
			.expect_log(log_executed(Precompile1, proposal_hash))
213
1
			.execute_returns(0u32);
214
1

            
215
1
		assert_event_emitted!(pallet_collective::Event::Executed {
216
1
			proposal_hash,
217
1
			result: Err(DispatchError::BadOrigin)
218
1
		}
219
1
		.into());
220
1
	});
221
1
}
222

            
223
#[test]
224
1
fn member_can_make_delayed_proposal() {
225
1
	ExtBuilder::default().build().execute_with(|| {
226
1
		let proposal = pallet_treasury::Call::<Runtime>::spend_local {
227
1
			amount: 1,
228
1
			beneficiary: Alice.into(),
229
1
		};
230
1
		let proposal: <Runtime as frame_system::Config>::RuntimeCall = proposal.into();
231
1
		let proposal = proposal.encode();
232
1
		let proposal_hash: H256 = hash::<Runtime>(&proposal);
233
1

            
234
1
		precompiles()
235
1
			.prepare_test(
236
1
				Bob,
237
1
				Precompile1,
238
1
				PCall::propose {
239
1
					threshold: 2,
240
1
					proposal: proposal.into(),
241
1
				},
242
1
			)
243
1
			.expect_log(log_proposed(Precompile1, Bob, 0, proposal_hash, 2))
244
1
			.execute_returns(0u32);
245
1

            
246
1
		assert_event_emitted!(pallet_collective::Event::Proposed {
247
1
			account: Bob.into(),
248
1
			proposal_index: 0,
249
1
			proposal_hash,
250
1
			threshold: 2,
251
1
		}
252
1
		.into());
253
1
	});
254
1
}
255

            
256
#[test]
257
1
fn member_can_vote_on_proposal() {
258
1
	ExtBuilder::default().build().execute_with(|| {
259
1
		let proposal = pallet_treasury::Call::<Runtime>::spend_local {
260
1
			amount: 1,
261
1
			beneficiary: Alice.into(),
262
1
		};
263
1
		let proposal: <Runtime as frame_system::Config>::RuntimeCall = proposal.into();
264
1
		let proposal = proposal.encode();
265
1
		let proposal_hash: H256 = hash::<Runtime>(&proposal);
266
1

            
267
1
		precompiles()
268
1
			.prepare_test(
269
1
				Bob,
270
1
				Precompile1,
271
1
				PCall::propose {
272
1
					threshold: 2,
273
1
					proposal: proposal.into(),
274
1
				},
275
1
			)
276
1
			.expect_log(log_proposed(Precompile1, Bob, 0, proposal_hash, 2))
277
1
			.execute_returns(0u32);
278
1

            
279
1
		precompiles()
280
1
			.prepare_test(
281
1
				Charlie,
282
1
				Precompile1,
283
1
				PCall::vote {
284
1
					proposal_hash,
285
1
					proposal_index: 0,
286
1
					approve: true,
287
1
				},
288
1
			)
289
1
			.expect_log(log_voted(Precompile1, Charlie, proposal_hash, true))
290
1
			.execute_returns(());
291
1

            
292
1
		assert_event_emitted!(pallet_collective::Event::Voted {
293
1
			account: Charlie.into(),
294
1
			proposal_hash,
295
1
			voted: true,
296
1
			yes: 1,
297
1
			no: 0,
298
1
		}
299
1
		.into());
300
1
	});
301
1
}
302

            
303
#[test]
304
1
fn cannot_close_if_not_enough_votes() {
305
1
	ExtBuilder::default().build().execute_with(|| {
306
1
		let proposal = pallet_treasury::Call::<Runtime>::spend_local {
307
1
			amount: 1,
308
1
			beneficiary: Alice.into(),
309
1
		};
310
1
		let proposal: <Runtime as frame_system::Config>::RuntimeCall = proposal.into();
311
1
		let proposal = proposal.encode();
312
1
		let proposal_hash: H256 = hash::<Runtime>(&proposal);
313
1
		let length_bound = proposal.len() as u32;
314
1

            
315
1
		precompiles()
316
1
			.prepare_test(
317
1
				Bob,
318
1
				Precompile1,
319
1
				PCall::propose {
320
1
					threshold: 2,
321
1
					proposal: proposal.into(),
322
1
				},
323
1
			)
324
1
			.expect_log(log_proposed(Precompile1, Bob, 0, proposal_hash, 2))
325
1
			.execute_returns(0u32);
326
1

            
327
1
		precompiles()
328
1
			.prepare_test(
329
1
				Alice,
330
1
				Precompile1,
331
1
				PCall::close {
332
1
					proposal_hash,
333
1
					proposal_index: 0,
334
1
					proposal_weight_bound: 10_000_000,
335
1
					length_bound,
336
1
				},
337
1
			)
338
1
			.expect_no_logs()
339
1
			.execute_reverts(|output| output.ends_with(b"TooEarly\") })"));
340
1
	});
341
1
}
342

            
343
#[test]
344
1
fn can_close_execute_if_enough_votes() {
345
1
	ExtBuilder::default().build().execute_with(|| {
346
1
		let proposal = pallet_treasury::Call::<Runtime>::spend_local {
347
1
			amount: 1,
348
1
			beneficiary: Alice.into(),
349
1
		};
350
1
		let proposal: <Runtime as frame_system::Config>::RuntimeCall = proposal.into();
351
1
		let proposal = proposal.encode();
352
1
		let proposal_hash: H256 = hash::<Runtime>(&proposal);
353
1
		let length_bound = proposal.len() as u32;
354
1

            
355
1
		precompiles()
356
1
			.prepare_test(
357
1
				Bob,
358
1
				Precompile1,
359
1
				PCall::propose {
360
1
					threshold: 2,
361
1
					proposal: proposal.into(),
362
1
				},
363
1
			)
364
1
			.expect_log(log_proposed(Precompile1, Bob, 0, proposal_hash, 2))
365
1
			.execute_returns(0u32);
366
1

            
367
1
		precompiles()
368
1
			.prepare_test(
369
1
				Bob,
370
1
				Precompile1,
371
1
				PCall::vote {
372
1
					proposal_hash,
373
1
					proposal_index: 0,
374
1
					approve: true,
375
1
				},
376
1
			)
377
1
			.expect_log(log_voted(Precompile1, Bob, proposal_hash, true))
378
1
			.execute_returns(());
379
1

            
380
1
		precompiles()
381
1
			.prepare_test(
382
1
				Charlie,
383
1
				Precompile1,
384
1
				PCall::vote {
385
1
					proposal_hash,
386
1
					proposal_index: 0,
387
1
					approve: true,
388
1
				},
389
1
			)
390
1
			.expect_log(log_voted(Precompile1, Charlie, proposal_hash, true))
391
1
			.execute_returns(());
392
1

            
393
1
		precompiles()
394
1
			.prepare_test(
395
1
				Alice,
396
1
				Precompile1,
397
1
				PCall::close {
398
1
					proposal_hash,
399
1
					proposal_index: 0,
400
1
					proposal_weight_bound: 200_000_000,
401
1
					length_bound,
402
1
				},
403
1
			)
404
1
			.expect_log(log_executed(Precompile1, proposal_hash))
405
1
			.execute_returns(true);
406
1

            
407
1
		assert_event_emitted!(pallet_collective::Event::Closed {
408
1
			proposal_hash,
409
1
			yes: 2,
410
1
			no: 0,
411
1
		}
412
1
		.into());
413

            
414
1
		assert_event_emitted!(pallet_collective::Event::Approved { proposal_hash }.into());
415

            
416
1
		assert_event_emitted!(pallet_collective::Event::Executed {
417
1
			proposal_hash,
418
1
			result: Ok(())
419
1
		}
420
1
		.into());
421

            
422
1
		assert_event_emitted!(pallet_treasury::Event::SpendApproved {
423
1
			proposal_index: 0,
424
1
			amount: 1,
425
1
			beneficiary: Alice.into(),
426
1
		}
427
1
		.into());
428
1
	});
429
1
}
430

            
431
#[test]
432
1
fn can_close_refuse_if_enough_votes() {
433
1
	ExtBuilder::default().build().execute_with(|| {
434
1
		let proposal = pallet_treasury::Call::<Runtime>::spend_local {
435
1
			amount: 1,
436
1
			beneficiary: Alice.into(),
437
1
		};
438
1
		let proposal: <Runtime as frame_system::Config>::RuntimeCall = proposal.into();
439
1
		let proposal = proposal.encode();
440
1
		let proposal_hash: H256 = hash::<Runtime>(&proposal);
441
1
		let length_bound = proposal.len() as u32;
442
1

            
443
1
		precompiles()
444
1
			.prepare_test(
445
1
				Bob,
446
1
				Precompile1,
447
1
				PCall::propose {
448
1
					threshold: 2,
449
1
					proposal: proposal.into(),
450
1
				},
451
1
			)
452
1
			.expect_log(log_proposed(Precompile1, Bob, 0, proposal_hash, 2))
453
1
			.execute_returns(0u32);
454
1

            
455
1
		precompiles()
456
1
			.prepare_test(
457
1
				Bob,
458
1
				Precompile1,
459
1
				PCall::vote {
460
1
					proposal_hash,
461
1
					proposal_index: 0,
462
1
					approve: false,
463
1
				},
464
1
			)
465
1
			.expect_log(log_voted(Precompile1, Bob, proposal_hash, false))
466
1
			.execute_returns(());
467
1

            
468
1
		precompiles()
469
1
			.prepare_test(
470
1
				Charlie,
471
1
				Precompile1,
472
1
				PCall::vote {
473
1
					proposal_hash,
474
1
					proposal_index: 0,
475
1
					approve: false,
476
1
				},
477
1
			)
478
1
			.expect_log(log_voted(Precompile1, Charlie, proposal_hash, false))
479
1
			.execute_returns(());
480
1

            
481
1
		precompiles()
482
1
			.prepare_test(
483
1
				Alice,
484
1
				Precompile1,
485
1
				PCall::close {
486
1
					proposal_hash,
487
1
					proposal_index: 0,
488
1
					proposal_weight_bound: 100_000_000,
489
1
					length_bound,
490
1
				},
491
1
			)
492
1
			.expect_log(log_closed(Precompile1, proposal_hash))
493
1
			.execute_returns(false);
494
1

            
495
1
		assert_event_emitted!(pallet_collective::Event::Closed {
496
1
			proposal_hash,
497
1
			yes: 0,
498
1
			no: 2,
499
1
		}
500
1
		.into());
501

            
502
1
		assert_event_emitted!(pallet_collective::Event::Disapproved { proposal_hash }.into());
503
1
	});
504
1
}
505

            
506
#[test]
507
1
fn multiple_propose_increase_index() {
508
1
	ExtBuilder::default().build().execute_with(|| {
509
1
		let proposal = pallet_treasury::Call::<Runtime>::spend_local {
510
1
			amount: 1,
511
1
			beneficiary: Alice.into(),
512
1
		};
513
1
		let proposal: <Runtime as frame_system::Config>::RuntimeCall = proposal.into();
514
1
		let proposal = proposal.encode();
515
1
		let proposal_hash: H256 = hash::<Runtime>(&proposal);
516
1

            
517
1
		precompiles()
518
1
			.prepare_test(
519
1
				Bob,
520
1
				Precompile1,
521
1
				PCall::propose {
522
1
					threshold: 2,
523
1
					proposal: proposal.into(),
524
1
				},
525
1
			)
526
1
			.expect_log(log_proposed(Precompile1, Bob, 0, proposal_hash, 2))
527
1
			.execute_returns(0u32);
528
1

            
529
1
		let proposal = pallet_treasury::Call::<Runtime>::spend_local {
530
1
			amount: 2,
531
1
			beneficiary: Alice.into(),
532
1
		};
533
1
		let proposal: <Runtime as frame_system::Config>::RuntimeCall = proposal.into();
534
1
		let proposal = proposal.encode();
535
1
		let proposal_hash: H256 = hash::<Runtime>(&proposal);
536
1

            
537
1
		precompiles()
538
1
			.prepare_test(
539
1
				Bob,
540
1
				Precompile1,
541
1
				PCall::propose {
542
1
					threshold: 2,
543
1
					proposal: proposal.into(),
544
1
				},
545
1
			)
546
1
			.expect_log(log_proposed(Precompile1, Bob, 1, proposal_hash, 2))
547
1
			.execute_returns(1u32);
548
1
	});
549
1
}
550

            
551
#[test]
552
1
fn view_members() {
553
1
	ExtBuilder::default().build().execute_with(|| {
554
1
		precompiles()
555
1
			.prepare_test(Bob, Precompile1, PCall::members {})
556
1
			.expect_no_logs()
557
1
			.execute_returns(vec![Address(Bob.into()), Address(Charlie.into())]);
558
1
	});
559
1
}
560

            
561
#[test]
562
1
fn view_no_prime() {
563
1
	ExtBuilder::default().build().execute_with(|| {
564
1
		precompiles()
565
1
			.prepare_test(Bob, Precompile1, PCall::prime {})
566
1
			.expect_no_logs()
567
1
			.execute_returns(Address(H160::zero()));
568
1
	});
569
1
}
570

            
571
#[test]
572
1
fn view_some_prime() {
573
1
	ExtBuilder::default().build().execute_with(|| {
574
1
		assert_ok!(pallet_collective::Pallet::<
575
1
			Runtime,
576
1
			pallet_collective::Instance1,
577
1
		>::set_members(
578
1
			RuntimeOrigin::root(),
579
1
			vec![Alice.into(), Bob.into()],
580
1
			Some(Alice.into()),
581
1
			2
582
1
		));
583

            
584
1
		precompiles()
585
1
			.prepare_test(Bob, Precompile1, PCall::prime {})
586
1
			.expect_no_logs()
587
1
			.execute_returns(Address(Alice.into()));
588
1
	});
589
1
}
590

            
591
#[test]
592
1
fn view_is_member() {
593
1
	ExtBuilder::default().build().execute_with(|| {
594
1
		precompiles()
595
1
			.prepare_test(
596
1
				Bob,
597
1
				Precompile1,
598
1
				PCall::is_member {
599
1
					account: Address(Bob.into()),
600
1
				},
601
1
			)
602
1
			.expect_no_logs()
603
1
			.execute_returns(true);
604
1

            
605
1
		precompiles()
606
1
			.prepare_test(
607
1
				Bob,
608
1
				Precompile1,
609
1
				PCall::is_member {
610
1
					account: Address(Alice.into()),
611
1
				},
612
1
			)
613
1
			.expect_no_logs()
614
1
			.execute_returns(false);
615
1
	});
616
1
}
617

            
618
mod bounded_proposal_decode {
619
	use super::*;
620
	use crate::GetProposalLimit;
621
	use precompile_utils::prelude::BoundedBytes;
622

            
623
4
	fn scenario<F>(nesting: usize, call: F)
624
4
	where
625
4
		F: FnOnce(BoundedBytes<GetProposalLimit>) -> PCall,
626
4
	{
627
4
		ExtBuilder::default().build().execute_with(|| {
628
4
			// Some random call.
629
4
			let mut proposal = pallet_collective::Call::<Runtime, Instance1>::set_members {
630
4
				new_members: Vec::new(),
631
4
				prime: None,
632
4
				old_count: 0,
633
4
			};
634
4

            
635
4
			// Nest it.
636
30
			for _ in 0..nesting {
637
30
				proposal = pallet_collective::Call::<Runtime, Instance1>::propose {
638
30
					threshold: 10,
639
30
					proposal: Box::new(proposal.into()),
640
30
					length_bound: 1,
641
30
				};
642
30
			}
643

            
644
4
			let proposal: <Runtime as frame_system::Config>::RuntimeCall = proposal.into();
645
4
			let proposal = proposal.encode();
646
4

            
647
4
			precompiles()
648
4
				.prepare_test(Alice, Precompile1, call(proposal.into()))
649
4
				.expect_no_logs()
650
4
				.execute_reverts(|output| {
651
4
					if nesting < 8 {
652
2
						output.ends_with(b"NotMember\") })")
653
					} else {
654
2
						output == b"proposal: Failed to decode proposal"
655
					}
656
4
				});
657
4
		});
658
4
	}
659

            
660
	#[test]
661
1
	fn proposal_above_bound() {
662
1
		scenario(8, |proposal| PCall::propose {
663
1
			threshold: 1,
664
1
			proposal,
665
1
		});
666
1
	}
667

            
668
	#[test]
669
1
	fn proposal_below_bound() {
670
1
		scenario(7, |proposal| PCall::propose {
671
1
			threshold: 1,
672
1
			proposal,
673
1
		});
674
1
	}
675

            
676
	#[test]
677
1
	fn execute_above_bound() {
678
1
		scenario(8, |proposal| PCall::execute { proposal });
679
1
	}
680

            
681
	#[test]
682
1
	fn execute_below_bound() {
683
1
		scenario(7, |proposal| PCall::execute { proposal });
684
1
	}
685
}