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 frame_support::assert_ok;
24
use pallet_evm::{Call as EvmCall, Event as EvmEvent};
25
use pallet_referenda::Call as ReferendaCall;
26

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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