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
#![cfg_attr(not(feature = "std"), no_std)]
18

            
19
use fp_evm::PrecompileHandle;
20
use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo};
21
use frame_support::traits::{
22
	schedule::DispatchTime, Bounded, Currency, Get, OriginTrait, VoteTally,
23
};
24
use pallet_evm::AddressMapping;
25
use pallet_referenda::{
26
	Call as ReferendaCall, DecidingCount, Deposit, Pallet as Referenda, ReferendumCount,
27
	ReferendumInfo, ReferendumInfoFor, TracksInfo,
28
};
29
use parity_scale_codec::{Encode, MaxEncodedLen};
30
use precompile_utils::prelude::*;
31
use sp_core::{H160, H256, U256};
32
use sp_runtime::str_array;
33
use sp_runtime::traits::Dispatchable;
34
use sp_std::str;
35
use sp_std::{boxed::Box, marker::PhantomData, str::FromStr, vec::Vec};
36

            
37
#[cfg(test)]
38
mod mock;
39
#[cfg(test)]
40
mod tests;
41

            
42
pub const CALL_DATA_LIMIT: u32 = 2u32.pow(16);
43

            
44
type BlockNumberFor<T> = pallet_referenda::BlockNumberFor<T, ()>;
45
type BalanceOf<Runtime> = <<Runtime as pallet_referenda::Config>::Currency as Currency<
46
	<Runtime as frame_system::Config>::AccountId,
47
>>::Balance;
48
type TrackIdOf<Runtime> = <<Runtime as pallet_referenda::Config>::Tracks as TracksInfo<
49
	BalanceOf<Runtime>,
50
	BlockNumberFor<Runtime>,
51
>>::Id;
52
type BoundedCallOf<Runtime> = Bounded<
53
	<Runtime as pallet_referenda::Config>::RuntimeCall,
54
	<Runtime as frame_system::Config>::Hashing,
55
>;
56

            
57
type OriginOf<Runtime> =
58
	<<Runtime as frame_system::Config>::RuntimeOrigin as OriginTrait>::PalletsOrigin;
59

            
60
pub(crate) const SELECTOR_LOG_SUBMITTED_AT: [u8; 32] =
61
	keccak256!("SubmittedAt(uint16,uint32,bytes32)");
62

            
63
pub(crate) const SELECTOR_LOG_SUBMITTED_AFTER: [u8; 32] =
64
	keccak256!("SubmittedAfter(uint16,uint32,bytes32)");
65

            
66
pub(crate) const SELECTOR_LOG_DECISION_DEPOSIT_PLACED: [u8; 32] =
67
	keccak256!("DecisionDepositPlaced(uint32,address,uint256)");
68

            
69
pub(crate) const SELECTOR_LOG_DECISION_DEPOSIT_REFUNDED: [u8; 32] =
70
	keccak256!("DecisionDepositRefunded(uint32,address,uint256)");
71

            
72
pub(crate) const SELECTOR_LOG_SUBMISSION_DEPOSIT_REFUNDED: [u8; 32] =
73
	keccak256!("SubmissionDepositRefunded(uint32,address,uint256)");
74

            
75
#[derive(solidity::Codec)]
76
pub struct TrackInfo {
77
	name: UnboundedBytes,
78
	max_deciding: U256,
79
	decision_deposit: U256,
80
	prepare_period: U256,
81
	decision_period: U256,
82
	confirm_period: U256,
83
	min_enactment_period: U256,
84
	min_approval: UnboundedBytes,
85
	min_support: UnboundedBytes,
86
}
87

            
88
#[derive(solidity::Codec)]
89
pub struct OngoingReferendumInfo {
90
	/// The track of this referendum.
91
	track_id: u16,
92
	/// The origin for this referendum.
93
	origin: UnboundedBytes,
94
	/// The hash of the proposal up for referendum.
95
	proposal: UnboundedBytes,
96
	/// Whether proposal is scheduled for enactment at or after `enactment_time`.
97
	enactment_type: bool,
98
	/// The time the proposal should be scheduled for enactment.
99
	enactment_time: U256,
100
	/// The time of submission. Once `UndecidingTimeout` passes, it may be closed by anyone if
101
	/// `deciding` is `None`.
102
	submission_time: U256,
103
	submission_depositor: Address,
104
	submission_deposit: U256,
105
	decision_depositor: Address,
106
	decision_deposit: U256,
107
	/// When this referendum began being "decided". If confirming, then the
108
	/// end will actually be delayed until the end of the confirmation period.
109
	deciding_since: U256,
110
	/// If nonzero, then the referendum has entered confirmation stage and will end at
111
	/// the block number as long as it doesn't lose its approval in the meantime.
112
	deciding_confirming_end: U256,
113
	/// The number of aye votes, expressed in terms of post-conviction lock-vote.
114
	ayes: U256,
115
	/// Percent aye votes, expressed pre-conviction, over the total votes in the class.
116
	support: u32,
117
	/// Percent of aye votes over aye + nay votes.
118
	approval: u32,
119
	/// Whether we have been placed in the queue for being decided or not.
120
	in_queue: bool,
121
	/// The next scheduled wake-up
122
	alarm_time: U256,
123
	alarm_task_address: UnboundedBytes,
124
}
125

            
126
#[derive(solidity::Codec)]
127
pub struct ClosedReferendumInfo {
128
	status: u8,
129
	end: U256,
130
	submission_depositor: Address,
131
	submission_deposit: U256,
132
	decision_depositor: Address,
133
	decision_deposit: U256,
134
}
135

            
136
/// A precompile to wrap the functionality from pallet-referenda.
137
pub struct ReferendaPrecompile<Runtime, GovOrigin>(PhantomData<(Runtime, GovOrigin)>);
138

            
139
22
#[precompile_utils::precompile]
140
impl<Runtime, GovOrigin> ReferendaPrecompile<Runtime, GovOrigin>
141
where
142
	Runtime: pallet_referenda::Config + pallet_evm::Config + frame_system::Config,
143
	OriginOf<Runtime>: From<GovOrigin>,
144
	Runtime::AccountId: Into<H160>,
145
	<Runtime as frame_system::Config>::RuntimeCall:
146
		Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
147
	<<Runtime as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin:
148
		From<Option<Runtime::AccountId>>,
149
	<Runtime as frame_system::Config>::RuntimeCall: From<ReferendaCall<Runtime>>,
150
	<Runtime as frame_system::Config>::Hash: Into<H256>,
151
	BlockNumberFor<Runtime>: Into<U256>,
152
	Runtime::AccountId: Into<H160>,
153
	TrackIdOf<Runtime>: TryFrom<u16> + TryInto<u16>,
154
	BalanceOf<Runtime>: Into<U256>,
155
	Runtime::Votes: Into<U256>,
156
	GovOrigin: FromStr,
157
	H256: From<<Runtime as frame_system::Config>::Hash>
158
		+ Into<<Runtime as frame_system::Config>::Hash>,
159
	<Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
160
{
161
	// The accessors are first. They directly return their result.
162
	#[precompile::public("referendumCount()")]
163
	#[precompile::view]
164
	fn referendum_count(handle: &mut impl PrecompileHandle) -> EvmResult<u32> {
165
		// ReferendumCount
166
		handle.record_db_read::<Runtime>(4)?;
167
		let ref_count = ReferendumCount::<Runtime>::get();
168
		log::trace!(target: "referendum-precompile", "Referendum count is {:?}", ref_count);
169

            
170
		Ok(ref_count)
171
	}
172

            
173
	#[precompile::public("submissionDeposit()")]
174
	#[precompile::view]
175
	fn submission_deposit(_handle: &mut impl PrecompileHandle) -> EvmResult<U256> {
176
		let submission_deposit = Runtime::SubmissionDeposit::get();
177
		log::trace!(target: "referendum-precompile", "Submission deposit is {:?}", submission_deposit);
178

            
179
		Ok(submission_deposit.into())
180
	}
181

            
182
	#[precompile::public("decidingCount(uint16)")]
183
	#[precompile::view]
184
	fn deciding_count(handle: &mut impl PrecompileHandle, track_id: u16) -> EvmResult<U256> {
185
		// DecidingCount:
186
		// Twox64Concat(8) + TrackIdOf(2) + 4
187
		handle.record_db_read::<Runtime>(14)?;
188
		let track_id: TrackIdOf<Runtime> = track_id
189
			.try_into()
190
			.map_err(|_| RevertReason::value_is_too_large("Track id type").into())
191
			.in_field("trackId")?;
192
		let deciding_count = DecidingCount::<Runtime>::get(track_id);
193
		log::trace!(
194
			target: "referendum-precompile", "Track {:?} deciding count is {:?}",
195
			track_id,
196
			deciding_count
197
		);
198

            
199
		Ok(deciding_count.into())
200
	}
201

            
202
	#[precompile::public("trackIds()")]
203
	#[precompile::view]
204
	fn track_ids(_handle: &mut impl PrecompileHandle) -> EvmResult<Vec<u16>> {
205
		let track_ids: Vec<u16> = Runtime::Tracks::tracks()
206
			.into_iter()
207
			.filter_map(|track| {
208
				if let Ok(track_id) = track.id.try_into() {
209
					Some(track_id)
210
				} else {
211
					None
212
				}
213
			})
214
			.collect();
215

            
216
		Ok(track_ids)
217
	}
218

            
219
	#[precompile::public("trackInfo(uint16)")]
220
	#[precompile::view]
221
	fn track_info(_handle: &mut impl PrecompileHandle, track_id: u16) -> EvmResult<TrackInfo> {
222
		let track_id: TrackIdOf<Runtime> = track_id
223
			.try_into()
224
			.map_err(|_| RevertReason::value_is_too_large("Track id type").into())
225
			.in_field("trackId")?;
226
		let track = Runtime::Tracks::tracks()
227
			.find(|track| track.id == track_id)
228
			.ok_or(RevertReason::custom("No such track").in_field("trackId"))?;
229
		let track_info = &track.info;
230

            
231
		// trim the nulls bytes at the end of the name
232
		// caused by this https://github.com/paritytech/polkadot-sdk/pull/2072
233
		let track_name_trimmed: &[u8] = track_info
234
			.name
235
			.iter()
236
			.rposition(|&b| b != 0)
237
			.map_or(&[], |pos| &track_info.name[..=pos]);
238

            
239
		Ok(TrackInfo {
240
			name: track_name_trimmed.into(),
241
			max_deciding: track_info.max_deciding.into(),
242
			decision_deposit: track_info.decision_deposit.into(),
243
			prepare_period: track_info.prepare_period.into(),
244
			decision_period: track_info.decision_period.into(),
245
			confirm_period: track_info.confirm_period.into(),
246
			min_enactment_period: track_info.min_enactment_period.into(),
247
			min_approval: track_info.min_approval.encode().into(),
248
			min_support: track_info.min_support.encode().into(),
249
		})
250
	}
251

            
252
	/// Use Runtime::Tracks::tracks to get the origin for input trackId
253
6
	fn track_id_to_origin(track_id: TrackIdOf<Runtime>) -> EvmResult<Box<OriginOf<Runtime>>> {
254
6
		let track = Runtime::Tracks::tracks()
255
7
			.find(|track| track.id == track_id)
256
6
			.ok_or(RevertReason::custom("No such track").in_field("trackId"))?;
257
5
		let track_info = &track.info;
258
5
		let origin = if track_info.name == str_array("root") {
259
5
			frame_system::RawOrigin::Root.into()
260
		} else {
261
			GovOrigin::from_str(str::from_utf8(&track_info.name).map_err(|_| {
262
				RevertReason::custom("Track name is not valid UTF-8").in_field("trackId")
263
			})?)
264
			.map_err(|_| {
265
				RevertReason::custom("Custom origin does not exist for {track_info.name}")
266
					.in_field("trackId")
267
			})?
268
			.into()
269
		};
270
5
		Ok(Box::new(origin))
271
6
	}
272

            
273
	// Helper function for submitAt and submitAfter
274
6
	fn submit(
275
6
		handle: &mut impl PrecompileHandle,
276
6
		track_id: u16,
277
6
		proposal: BoundedCallOf<Runtime>,
278
6
		enactment_moment: DispatchTime<BlockNumberFor<Runtime>>,
279
6
	) -> EvmResult<u32> {
280
6
		log::trace!(
281
			target: "referendum-precompile",
282
			"Submitting proposal {:?} [len: {:?}] to track {}",
283
			proposal.hash(),
284
			proposal.len(),
285
			track_id
286
		);
287
		// ReferendumCount
288
6
		handle.record_db_read::<Runtime>(4)?;
289
6
		let referendum_index = ReferendumCount::<Runtime>::get();
290

            
291
5
		let proposal_origin = Self::track_id_to_origin(
292
6
			track_id
293
6
				.try_into()
294
6
				.map_err(|_| RevertReason::value_is_too_large("Track id type").into())
295
6
				.in_field("trackId")?,
296
1
		)?;
297
5
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
298
5

            
299
5
		let call = ReferendaCall::<Runtime>::submit {
300
5
			proposal_origin,
301
5
			proposal,
302
5
			enactment_moment,
303
5
		}
304
5
		.into();
305
5

            
306
5
		<RuntimeHelper<Runtime>>::try_dispatch(handle, Some(origin).into(), call, 0)?;
307

            
308
5
		Ok(referendum_index)
309
6
	}
310

            
311
	#[precompile::public("referendumStatus(uint32)")]
312
	#[precompile::view]
313
	fn referendum_status(
314
		handle: &mut impl PrecompileHandle,
315
		referendum_index: u32,
316
	) -> EvmResult<u8> {
317
		// ReferendumInfoFor: Blake2128(16) + 4 + ReferendumInfoOf::max_encoded_len
318
		handle.record_db_read::<Runtime>(
319
			20 + pallet_referenda::ReferendumInfoOf::<Runtime, ()>::max_encoded_len(),
320
		)?;
321

            
322
		let status = match ReferendumInfoFor::<Runtime>::get(referendum_index).ok_or(
323
			RevertReason::custom("Referendum does not exist for index")
324
				.in_field("referendum_index"),
325
		)? {
326
			ReferendumInfo::Ongoing(..) => 0,
327
			ReferendumInfo::Approved(..) => 1,
328
			ReferendumInfo::Rejected(..) => 2,
329
			ReferendumInfo::Cancelled(..) => 3,
330
			ReferendumInfo::TimedOut(..) => 4,
331
			ReferendumInfo::Killed(..) => 5,
332
		};
333

            
334
		Ok(status)
335
	}
336

            
337
	#[precompile::public("ongoingReferendumInfo(uint32)")]
338
	#[precompile::view]
339
	fn ongoing_referendum_info(
340
		handle: &mut impl PrecompileHandle,
341
		referendum_index: u32,
342
	) -> EvmResult<OngoingReferendumInfo> {
343
		// ReferendumInfoFor: Blake2128(16) + 4 + ReferendumInfoOf::max_encoded_len
344
		handle.record_db_read::<Runtime>(
345
			20 + pallet_referenda::ReferendumInfoOf::<Runtime, ()>::max_encoded_len(),
346
		)?;
347

            
348
		match ReferendumInfoFor::<Runtime>::get(referendum_index).ok_or(
349
			RevertReason::custom("Referendum does not exist for index")
350
				.in_field("referendum_index"),
351
		)? {
352
			ReferendumInfo::Ongoing(info) => {
353
				let track_id = info
354
					.track
355
					.try_into()
356
					.map_err(|_| RevertReason::value_is_too_large("Track id type not u16"))?;
357
				let (enactment_type, enactment_time) = match info.enactment {
358
					DispatchTime::At(x) => (true, x.into()),
359
					DispatchTime::After(x) => (false, x.into()),
360
				};
361
				let (decision_depositor, decision_deposit) =
362
					if let Some(deposit) = info.decision_deposit {
363
						(Address(deposit.who.into()), deposit.amount.into())
364
					} else {
365
						(Address(H160::zero()), U256::zero())
366
					};
367
				let (deciding_since, deciding_confirming_end) =
368
					if let Some(deciding_status) = info.deciding {
369
						(
370
							deciding_status.since.into(),
371
							deciding_status.confirming.unwrap_or_default().into(),
372
						)
373
					} else {
374
						(U256::zero(), U256::zero())
375
					};
376
				let (alarm_time, alarm_task_address) =
377
					if let Some((time, task_address)) = info.alarm {
378
						(time.into(), task_address.encode().into())
379
					} else {
380
						(U256::zero(), UnboundedBytes::from(&[]))
381
					};
382

            
383
				Ok(OngoingReferendumInfo {
384
					track_id,
385
					origin: info.origin.encode().into(),
386
					proposal: info.proposal.encode().into(),
387
					enactment_type,
388
					enactment_time,
389
					submission_time: info.submitted.into(),
390
					submission_depositor: Address(info.submission_deposit.who.into()),
391
					submission_deposit: info.submission_deposit.amount.into(),
392
					decision_depositor,
393
					decision_deposit,
394
					deciding_since,
395
					deciding_confirming_end,
396
					ayes: info.tally.ayes(info.track).into(),
397
					support: info.tally.support(info.track).deconstruct(),
398
					approval: info.tally.approval(info.track).deconstruct(),
399
					in_queue: info.in_queue,
400
					alarm_time,
401
					alarm_task_address,
402
				})
403
			}
404
			_ => Err(RevertReason::custom("Referendum not ongoing").into()),
405
		}
406
	}
407

            
408
	#[precompile::public("closedReferendumInfo(uint32)")]
409
	#[precompile::view]
410
	fn closed_referendum_info(
411
		handle: &mut impl PrecompileHandle,
412
		referendum_index: u32,
413
	) -> EvmResult<ClosedReferendumInfo> {
414
		// ReferendumInfoFor: Blake2128(16) + 4 + ReferendumInfoOf::max_encoded_len
415
		handle.record_db_read::<Runtime>(
416
			20 + pallet_referenda::ReferendumInfoOf::<Runtime, ()>::max_encoded_len(),
417
		)?;
418

            
419
		let get_closed_ref_info =
420
			|status,
421
			 moment: BlockNumberFor<Runtime>,
422
			 submission_deposit: Option<Deposit<Runtime::AccountId, BalanceOf<Runtime>>>,
423
			 decision_deposit: Option<Deposit<Runtime::AccountId, BalanceOf<Runtime>>>|
424
			 -> ClosedReferendumInfo {
425
				let (submission_depositor, submission_deposit_amount): (Address, U256) =
426
					if let Some(Deposit { who, amount }) = submission_deposit {
427
						(Address(who.into()), amount.into())
428
					} else {
429
						(Address(H160::zero()), U256::zero())
430
					};
431
				let (decision_depositor, decision_deposit_amount) =
432
					if let Some(Deposit { who, amount }) = decision_deposit {
433
						(Address(who.into()), amount.into())
434
					} else {
435
						(Address(H160::zero()), U256::zero())
436
					};
437
				ClosedReferendumInfo {
438
					status,
439
					end: moment.into(),
440
					submission_depositor,
441
					submission_deposit: submission_deposit_amount,
442
					decision_depositor,
443
					decision_deposit: decision_deposit_amount,
444
				}
445
			};
446

            
447
		match ReferendumInfoFor::<Runtime>::get(referendum_index).ok_or(
448
			RevertReason::custom("Referendum does not exist for index")
449
				.in_field("referendum_index"),
450
		)? {
451
			ReferendumInfo::Approved(moment, submission_deposit, decision_deposit) => Ok(
452
				get_closed_ref_info(1, moment, submission_deposit, decision_deposit),
453
			),
454
			ReferendumInfo::Rejected(moment, submission_deposit, decision_deposit) => Ok(
455
				get_closed_ref_info(2, moment, submission_deposit, decision_deposit),
456
			),
457
			ReferendumInfo::Cancelled(moment, submission_deposit, decision_deposit) => Ok(
458
				get_closed_ref_info(3, moment, submission_deposit, decision_deposit),
459
			),
460
			ReferendumInfo::TimedOut(moment, submission_deposit, decision_deposit) => Ok(
461
				get_closed_ref_info(4, moment, submission_deposit, decision_deposit),
462
			),
463
			_ => Err(RevertReason::custom("Referendum not closed").into()),
464
		}
465
	}
466

            
467
	#[precompile::public("killedReferendumBlock(uint32)")]
468
	#[precompile::view]
469
	fn killed_referendum_block(
470
		handle: &mut impl PrecompileHandle,
471
		referendum_index: u32,
472
	) -> EvmResult<U256> {
473
		// ReferendumInfoFor: Blake2128(16) + 4 + ReferendumInfoOf::max_encoded_len
474
		handle.record_db_read::<Runtime>(
475
			20 + pallet_referenda::ReferendumInfoOf::<Runtime, ()>::max_encoded_len(),
476
		)?;
477

            
478
		let block = match ReferendumInfoFor::<Runtime>::get(referendum_index).ok_or(
479
			RevertReason::custom("Referendum does not exist for index")
480
				.in_field("referendum_index"),
481
		)? {
482
			ReferendumInfo::Killed(b) => b,
483
			_ => return Err(RevertReason::custom("Referendum not killed").into()),
484
		};
485

            
486
		Ok(block.into())
487
	}
488

            
489
	/// Propose a referendum on a privileged action.
490
	///
491
	/// Parameters:
492
	/// * track_id: The trackId for the origin from which the proposal is to be dispatched.
493
	/// * proposal_hash: The proposed runtime call hash stored in the preimage pallet.
494
	/// * proposal_len: The proposed runtime call length.
495
	/// * block_number: Block number at which proposal is dispatched.
496
	#[precompile::public("submitAt(uint16,bytes32,uint32,uint32)")]
497
4
	fn submit_at(
498
4
		handle: &mut impl PrecompileHandle,
499
4
		track_id: u16,
500
4
		proposal_hash: H256,
501
4
		proposal_len: u32,
502
4
		block_number: u32,
503
4
	) -> EvmResult<u32> {
504
4
		let proposal: BoundedCallOf<Runtime> = Bounded::Lookup {
505
4
			hash: proposal_hash.into(),
506
4
			len: proposal_len,
507
4
		};
508
4
		handle.record_log_costs_manual(2, 32 * 2)?;
509

            
510
4
		let referendum_index = Self::submit(
511
4
			handle,
512
4
			track_id,
513
4
			proposal,
514
4
			DispatchTime::At(block_number.into()),
515
4
		)?;
516
3
		let event = log2(
517
3
			handle.context().address,
518
3
			SELECTOR_LOG_SUBMITTED_AT,
519
3
			H256::from_low_u64_be(track_id as u64),
520
3
			solidity::encode_event_data((referendum_index, proposal_hash)),
521
3
		);
522
3
		event.record(handle)?;
523

            
524
3
		Ok(referendum_index)
525
4
	}
526

            
527
	/// Propose a referendum on a privileged action.
528
	///
529
	/// Parameters:
530
	/// * track_id: The trackId for the origin from which the proposal is to be dispatched.
531
	/// * proposal_hash: The proposed runtime call hash stored in the preimage pallet.
532
	/// * proposal_len: The proposed runtime call length.
533
	/// * block_number: Block number after which proposal is dispatched.
534
	#[precompile::public("submitAfter(uint16,bytes32,uint32,uint32)")]
535
2
	fn submit_after(
536
2
		handle: &mut impl PrecompileHandle,
537
2
		track_id: u16,
538
2
		proposal_hash: H256,
539
2
		proposal_len: u32,
540
2
		block_number: u32,
541
2
	) -> EvmResult<u32> {
542
2
		let proposal: BoundedCallOf<Runtime> = Bounded::Lookup {
543
2
			hash: proposal_hash.into(),
544
2
			len: proposal_len,
545
2
		};
546
2
		handle.record_log_costs_manual(2, 32 * 2)?;
547

            
548
2
		let referendum_index = Self::submit(
549
2
			handle,
550
2
			track_id,
551
2
			proposal,
552
2
			DispatchTime::After(block_number.into()),
553
2
		)?;
554
2
		let event = log2(
555
2
			handle.context().address,
556
2
			SELECTOR_LOG_SUBMITTED_AFTER,
557
2
			H256::from_low_u64_be(track_id as u64),
558
2
			solidity::encode_event_data((referendum_index, proposal_hash)),
559
2
		);
560
2

            
561
2
		event.record(handle)?;
562

            
563
2
		Ok(referendum_index)
564
2
	}
565

            
566
	/// Post the Decision Deposit for a referendum.
567
	///
568
	/// Parameters:
569
	/// * index: The index of the submitted referendum whose Decision Deposit is yet to be posted.
570
	#[precompile::public("placeDecisionDeposit(uint32)")]
571
1
	fn place_decision_deposit(handle: &mut impl PrecompileHandle, index: u32) -> EvmResult {
572
1
		// ReferendumInfoFor: Blake2128(16) + 4 + ReferendumInfoOf::max_encoded_len
573
1
		handle.record_db_read::<Runtime>(
574
1
			20 + pallet_referenda::ReferendumInfoOf::<Runtime, ()>::max_encoded_len(),
575
1
		)?;
576
1
		handle.record_log_costs_manual(1, 32 * 3)?;
577

            
578
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
579
1

            
580
1
		let call = ReferendaCall::<Runtime>::place_decision_deposit { index }.into();
581
1

            
582
1
		<RuntimeHelper<Runtime>>::try_dispatch(handle, Some(origin).into(), call, 0)?;
583

            
584
		// Once the deposit has been succesfully placed, it is available in the ReferendumStatus.
585
1
		let ongoing_referendum = Referenda::<Runtime>::ensure_ongoing(index).map_err(|_| {
586
			RevertReason::custom("Provided index is not an ongoing referendum").in_field("index")
587
1
		})?;
588
1
		let decision_deposit: U256 =
589
1
			if let Some(decision_deposit) = ongoing_referendum.decision_deposit {
590
1
				decision_deposit.amount.into()
591
			} else {
592
				U256::zero()
593
			};
594
1
		let event = log1(
595
1
			handle.context().address,
596
1
			SELECTOR_LOG_DECISION_DEPOSIT_PLACED,
597
1
			solidity::encode_event_data((
598
1
				index,
599
1
				Address(handle.context().caller),
600
1
				decision_deposit,
601
1
			)),
602
1
		);
603
1

            
604
1
		event.record(handle)?;
605
1
		Ok(())
606
1
	}
607

            
608
	/// Refund the Decision Deposit for a closed referendum back to the depositor.
609
	///
610
	/// Parameters:
611
	/// * index: The index of a closed referendum whose Decision Deposit has not yet been refunded.
612
	#[precompile::public("refundDecisionDeposit(uint32)")]
613
1
	fn refund_decision_deposit(handle: &mut impl PrecompileHandle, index: u32) -> EvmResult {
614
1
		handle.record_log_costs_manual(1, 32 * 3)?;
615
		// ReferendumInfoFor: Blake2128(16) + 4 + ReferendumInfoOf::max_encoded_len
616
1
		handle.record_db_read::<Runtime>(
617
1
			20 + pallet_referenda::ReferendumInfoOf::<Runtime, ()>::max_encoded_len(),
618
1
		)?;
619
1
		let (who, refunded_deposit): (H160, U256) = match ReferendumInfoFor::<Runtime>::get(index)
620
1
			.ok_or(
621
1
			RevertReason::custom("Referendum index does not exist").in_field("index"),
622
1
		)? {
623
			ReferendumInfo::Approved(_, _, Some(d))
624
			| ReferendumInfo::Rejected(_, _, Some(d))
625
			| ReferendumInfo::TimedOut(_, _, Some(d))
626
1
			| ReferendumInfo::Cancelled(_, _, Some(d)) => (d.who.into(), d.amount.into()),
627
			// We let the pallet handle the RenferendumInfo validation logic on dispatch.
628
			_ => (H160::default(), U256::zero()),
629
		};
630

            
631
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
632
1

            
633
1
		let call = ReferendaCall::<Runtime>::refund_decision_deposit { index }.into();
634
1

            
635
1
		<RuntimeHelper<Runtime>>::try_dispatch(handle, Some(origin).into(), call, 0)?;
636
1
		let event = log1(
637
1
			handle.context().address,
638
1
			SELECTOR_LOG_DECISION_DEPOSIT_REFUNDED,
639
1
			solidity::encode_event_data((index, Address(who), refunded_deposit)),
640
1
		);
641
1

            
642
1
		event.record(handle)?;
643
1
		Ok(())
644
1
	}
645

            
646
	/// Refund the Submission Deposit for a closed referendum back to the depositor.
647
	///
648
	/// Parameters:
649
	/// * index: The index of a closed referendum whose Submission Deposit has not yet been refunded.
650
	#[precompile::public("refundSubmissionDeposit(uint32)")]
651
1
	fn refund_submission_deposit(handle: &mut impl PrecompileHandle, index: u32) -> EvmResult {
652
1
		handle.record_log_costs_manual(1, 32 * 3)?;
653
		// ReferendumInfoFor: Blake2128(16) + 4 + ReferendumInfoOf::max_encoded_len
654
1
		handle.record_db_read::<Runtime>(
655
1
			20 + pallet_referenda::ReferendumInfoOf::<Runtime, ()>::max_encoded_len(),
656
1
		)?;
657
1
		let (who, refunded_deposit): (H160, U256) =
658
1
			match ReferendumInfoFor::<Runtime>::get(index)
659
1
				.ok_or(RevertReason::custom("Referendum index does not exist").in_field("index"))?
660
			{
661
				ReferendumInfo::Approved(_, Some(s), _)
662
1
				| ReferendumInfo::Cancelled(_, Some(s), _) => (s.who.into(), s.amount.into()),
663
				// We let the pallet handle the RenferendumInfo validation logic on dispatch.
664
				_ => (H160::default(), U256::zero()),
665
			};
666

            
667
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
668
1

            
669
1
		let call = ReferendaCall::<Runtime>::refund_submission_deposit { index }.into();
670
1

            
671
1
		<RuntimeHelper<Runtime>>::try_dispatch(handle, Some(origin).into(), call, 0)?;
672

            
673
1
		let event = log1(
674
1
			handle.context().address,
675
1
			SELECTOR_LOG_SUBMISSION_DEPOSIT_REFUNDED,
676
1
			solidity::encode_event_data((index, Address(who), refunded_deposit)),
677
1
		);
678
1

            
679
1
		event.record(handle)?;
680

            
681
1
		Ok(())
682
1
	}
683
}