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
use crate::{
17
	mock::*, SELECTOR_LOG_DECISION_DEPOSIT_PLACED, SELECTOR_LOG_DECISION_DEPOSIT_REFUNDED,
18
	SELECTOR_LOG_SUBMISSION_DEPOSIT_REFUNDED, SELECTOR_LOG_SUBMITTED_AFTER,
19
	SELECTOR_LOG_SUBMITTED_AT,
20
};
21
use precompile_utils::{prelude::*, testing::*};
22

            
23
use fp_evm::MAX_TRANSACTION_GAS_LIMIT;
24
use frame_support::assert_ok;
25
use pallet_evm::{Call as EvmCall, Event as EvmEvent};
26
use pallet_referenda::Call as ReferendaCall;
27

            
28
use sp_core::{Hasher, H256, U256};
29
use sp_runtime::traits::Dispatchable;
30

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

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

            
50
#[test]
51
1
fn test_solidity_interface_has_all_function_selectors_documented_and_implemented() {
52
1
	check_precompile_implements_solidity_interfaces(&["Referenda.sol"], PCall::supports_selector)
53
1
}
54

            
55
#[test]
56
1
fn submitted_at_logs_work() {
57
1
	ExtBuilder::default()
58
1
		.with_balances(vec![(Alice.into(), 100_000)])
59
1
		.build()
60
1
		.execute_with(|| {
61
1
			let proposal = vec![1, 2, 3];
62
1
			let proposal_hash = sp_runtime::traits::BlakeTwo256::hash(&proposal);
63

            
64
			// Submit referendum at index 0
65
1
			let input = PCall::submit_at {
66
1
				track_id: 0u16,
67
1
				proposal_hash: proposal_hash,
68
1
				proposal_len: proposal.len() as u32,
69
1
				block_number: 0u32,
70
1
			}
71
1
			.into();
72
1
			assert_ok!(RuntimeCall::Evm(evm_call(input)).dispatch(RuntimeOrigin::root()));
73

            
74
			// Submit referendum at index 1
75
1
			let input = PCall::submit_at {
76
1
				track_id: 0u16,
77
1
				proposal_hash: proposal_hash,
78
1
				proposal_len: proposal.len() as u32,
79
1
				block_number: 0u32,
80
1
			}
81
1
			.into();
82
1
			assert_ok!(RuntimeCall::Evm(evm_call(input)).dispatch(RuntimeOrigin::root()));
83

            
84
1
			assert!(vec![
85
1
				EvmEvent::Log {
86
1
					log: log2(
87
1
						Precompile1,
88
1
						SELECTOR_LOG_SUBMITTED_AT,
89
1
						H256::from_low_u64_be(0u64),
90
1
						solidity::encode_event_data((
91
1
							0u32, // referendum index
92
1
							proposal_hash
93
1
						))
94
1
					),
95
1
				}
96
1
				.into(),
97
1
				EvmEvent::Log {
98
1
					log: log2(
99
1
						Precompile1,
100
1
						SELECTOR_LOG_SUBMITTED_AT,
101
1
						H256::from_low_u64_be(0u64),
102
1
						solidity::encode_event_data((
103
1
							1u32, // referendum index
104
1
							proposal_hash
105
1
						))
106
1
					),
107
1
				}
108
1
				.into()
109
1
			]
110
1
			.iter()
111
2
			.all(|log| events().contains(log)));
112
1
		});
113
1
}
114

            
115
#[test]
116
1
fn submitted_after_logs_work() {
117
1
	ExtBuilder::default()
118
1
		.with_balances(vec![(Alice.into(), 100_000)])
119
1
		.build()
120
1
		.execute_with(|| {
121
1
			let proposal = vec![1, 2, 3];
122
1
			let proposal_hash = sp_runtime::traits::BlakeTwo256::hash(&proposal);
123

            
124
			// Submit referendum at index 0
125
1
			let input = PCall::submit_after {
126
1
				track_id: 0u16,
127
1
				proposal_hash: proposal_hash,
128
1
				proposal_len: proposal.len() as u32,
129
1
				block_number: 0u32,
130
1
			}
131
1
			.into();
132
1
			assert_ok!(RuntimeCall::Evm(evm_call(input)).dispatch(RuntimeOrigin::root()));
133

            
134
			// Submit referendum at index 1
135
1
			let input = PCall::submit_after {
136
1
				track_id: 0u16,
137
1
				proposal_hash: proposal_hash,
138
1
				proposal_len: proposal.len() as u32,
139
1
				block_number: 0u32,
140
1
			}
141
1
			.into();
142
1
			assert_ok!(RuntimeCall::Evm(evm_call(input)).dispatch(RuntimeOrigin::root()));
143

            
144
1
			assert!(vec![
145
1
				EvmEvent::Log {
146
1
					log: log2(
147
1
						Precompile1,
148
1
						SELECTOR_LOG_SUBMITTED_AFTER,
149
1
						H256::from_low_u64_be(0u64),
150
1
						solidity::encode_event_data((
151
1
							0u32, // referendum index
152
1
							proposal_hash
153
1
						))
154
1
					),
155
1
				}
156
1
				.into(),
157
1
				EvmEvent::Log {
158
1
					log: log2(
159
1
						Precompile1,
160
1
						SELECTOR_LOG_SUBMITTED_AFTER,
161
1
						H256::from_low_u64_be(0u64),
162
1
						solidity::encode_event_data((
163
1
							1u32, // referendum index
164
1
							proposal_hash
165
1
						))
166
1
					),
167
1
				}
168
1
				.into()
169
1
			]
170
1
			.iter()
171
2
			.all(|log| events().contains(log)));
172
1
		});
173
1
}
174

            
175
#[test]
176
1
fn place_and_refund_decision_deposit_logs_work() {
177
1
	ExtBuilder::default()
178
1
		.with_balances(vec![(Alice.into(), 100_000)])
179
1
		.build()
180
1
		.execute_with(|| {
181
1
			let proposal = vec![1, 2, 3];
182
1
			let proposal_hash = sp_runtime::traits::BlakeTwo256::hash(&proposal);
183
1
			let referendum_index = 0u32;
184

            
185
			// Create referendum
186
1
			let input = PCall::submit_at {
187
1
				track_id: 0u16,
188
1
				proposal_hash: proposal_hash,
189
1
				proposal_len: proposal.len() as u32,
190
1
				block_number: 0u32,
191
1
			}
192
1
			.into();
193
1
			assert_ok!(RuntimeCall::Evm(evm_call(input)).dispatch(RuntimeOrigin::root()));
194

            
195
			// Place referendum decision deposit
196
1
			let input = PCall::place_decision_deposit {
197
1
				index: referendum_index,
198
1
			}
199
1
			.into();
200
1
			assert_ok!(RuntimeCall::Evm(evm_call(input)).dispatch(RuntimeOrigin::root()));
201

            
202
			// Assert all place events are emitted
203
1
			assert!(vec![
204
1
				RuntimeEvent::Referenda(pallet_referenda::pallet::Event::DecisionDepositPlaced {
205
1
					index: referendum_index,
206
1
					who: Alice.into(),
207
1
					amount: 10
208
1
				}),
209
1
				EvmEvent::Log {
210
1
					log: log1(
211
1
						Precompile1,
212
1
						SELECTOR_LOG_DECISION_DEPOSIT_PLACED,
213
1
						solidity::encode_event_data((
214
1
							referendum_index,
215
1
							Address(Alice.into()),
216
1
							U256::from(10), // decision deposit
217
1
						))
218
1
					)
219
1
				}
220
1
				.into()
221
1
			]
222
1
			.iter()
223
2
			.all(|log| events().contains(log)));
224

            
225
			// Cancel referendum so we can refund
226
1
			assert_ok!(RuntimeCall::Referenda(ReferendaCall::cancel {
227
1
				index: referendum_index,
228
1
			})
229
1
			.dispatch(RuntimeOrigin::signed(Alice.into())));
230

            
231
			// Refund referendum decision deposit
232
1
			let input = PCall::refund_decision_deposit {
233
1
				index: referendum_index,
234
1
			}
235
1
			.into();
236
1
			assert_ok!(RuntimeCall::Evm(evm_call(input)).dispatch(RuntimeOrigin::root()));
237

            
238
			// Refund referendum submission deposit.
239
			// Eligible because we cancelled the referendum.
240
1
			let input = PCall::refund_submission_deposit {
241
1
				index: referendum_index,
242
1
			}
243
1
			.into();
244
1
			assert_ok!(RuntimeCall::Evm(evm_call(input)).dispatch(RuntimeOrigin::root()));
245

            
246
			// Assert all refund events are emitted
247
1
			assert!(vec![
248
1
				RuntimeEvent::Referenda(pallet_referenda::pallet::Event::DecisionDepositRefunded {
249
1
					index: referendum_index,
250
1
					who: Alice.into(),
251
1
					amount: 10
252
1
				}),
253
1
				RuntimeEvent::Referenda(
254
1
					pallet_referenda::pallet::Event::SubmissionDepositRefunded {
255
1
						index: referendum_index,
256
1
						who: Alice.into(),
257
1
						amount: 15
258
1
					}
259
1
				),
260
1
				EvmEvent::Log {
261
1
					log: log1(
262
1
						Precompile1,
263
1
						SELECTOR_LOG_DECISION_DEPOSIT_REFUNDED,
264
1
						solidity::encode_event_data((
265
1
							referendum_index,
266
1
							Address(Alice.into()),
267
1
							U256::from(10), // decision deposit
268
1
						))
269
1
					)
270
1
				}
271
1
				.into(),
272
1
				EvmEvent::Log {
273
1
					log: log1(
274
1
						Precompile1,
275
1
						SELECTOR_LOG_SUBMISSION_DEPOSIT_REFUNDED,
276
1
						solidity::encode_event_data((
277
1
							referendum_index,
278
1
							Address(Alice.into()),
279
1
							U256::from(15), // submission deposit
280
1
						))
281
1
					)
282
1
				}
283
1
				.into()
284
1
			]
285
1
			.iter()
286
4
			.all(|log| events().contains(log)));
287
1
		});
288
1
}
289

            
290
#[test]
291
1
fn submit_track_id_oob_fails() {
292
	use pallet_referenda::TracksInfo;
293

            
294
1
	ExtBuilder::default()
295
1
		.with_balances(vec![(Alice.into(), 100_000)])
296
1
		.build()
297
1
		.execute_with(|| {
298
1
			let proposal = vec![1, 2, 3];
299
1
			let proposal_hash = sp_runtime::traits::BlakeTwo256::hash(&proposal);
300
1
			let oob_track_id =
301
1
				<crate::mock::Runtime as pallet_referenda::Config>::Tracks::tracks().count();
302

            
303
			// submit with an invalid track_id
304
1
			let input = PCall::submit_at {
305
1
				track_id: oob_track_id as u16,
306
1
				proposal_hash: proposal_hash,
307
1
				proposal_len: proposal.len() as u32,
308
1
				block_number: 0u32,
309
1
			};
310

            
311
1
			precompiles()
312
1
				.prepare_test(Alice, Precompile1, input)
313
1
				.execute_reverts(|output| output == b"trackId: No such track");
314
1
		});
315
1
}