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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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