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 frame_system::pallet_prelude::BlockNumberFor;
25
use pallet_evm::AddressMapping;
26
use pallet_referenda::{
27
	Call as ReferendaCall, DecidingCount, Deposit, Pallet as Referenda, ReferendumCount,
28
	ReferendumInfo, ReferendumInfoFor, TracksInfo,
29
};
30
use parity_scale_codec::{Encode, MaxEncodedLen};
31
use precompile_utils::prelude::*;
32
use sp_core::{H160, H256, U256};
33
use sp_runtime::traits::Dispatchable;
34
use sp_std::{boxed::Box, marker::PhantomData, str::FromStr, vec::Vec};
35

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

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

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

            
55
type OriginOf<Runtime> =
56
	<<Runtime as frame_system::Config>::RuntimeOrigin as OriginTrait>::PalletsOrigin;
57

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

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

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

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

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

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

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

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

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

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

            
168
		Ok(ref_count)
169
	}
170

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

            
177
		Ok(submission_deposit.into())
178
	}
179

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

            
197
		Ok(deciding_count.into())
198
	}
199

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

            
214
		Ok(track_ids)
215
	}
216

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

            
230
		Ok(TrackInfo {
231
			name: track_info.name.into(),
232
			max_deciding: track_info.max_deciding.into(),
233
			decision_deposit: track_info.decision_deposit.into(),
234
			prepare_period: track_info.prepare_period.into(),
235
			decision_period: track_info.decision_period.into(),
236
			confirm_period: track_info.confirm_period.into(),
237
			min_enactment_period: track_info.min_enactment_period.into(),
238
			min_approval: track_info.min_approval.encode().into(),
239
			min_support: track_info.min_support.encode().into(),
240
		})
241
	}
242

            
243
	/// Use Runtime::Tracks::tracks to get the origin for input trackId
244
6
	fn track_id_to_origin(track_id: TrackIdOf<Runtime>) -> EvmResult<Box<OriginOf<Runtime>>> {
245
6
		let track = Runtime::Tracks::tracks()
246
6
			.iter()
247
7
			.find(|(id, _)| *id == track_id)
248
6
			.ok_or(RevertReason::custom("No such track").in_field("trackId"))?;
249
5
		let track_info = &track.1;
250
5
		let origin = if track_info.name == "root" {
251
5
			frame_system::RawOrigin::Root.into()
252
		} else {
253
			GovOrigin::from_str(track_info.name)
254
				.map_err(|_| {
255
					RevertReason::custom("Custom origin does not exist for {track_info.name}")
256
						.in_field("trackId")
257
				})?
258
				.into()
259
		};
260
5
		Ok(Box::new(origin))
261
6
	}
262

            
263
	// Helper function for submitAt and submitAfter
264
6
	fn submit(
265
6
		handle: &mut impl PrecompileHandle,
266
6
		track_id: u16,
267
6
		proposal: BoundedCallOf<Runtime>,
268
6
		enactment_moment: DispatchTime<BlockNumberFor<Runtime>>,
269
6
	) -> EvmResult<u32> {
270
6
		log::trace!(
271
			target: "referendum-precompile",
272
			"Submitting proposal {:?} [len: {:?}] to track {}",
273
			proposal.hash(),
274
			proposal.len(),
275
			track_id
276
		);
277
		// ReferendumCount
278
6
		handle.record_db_read::<Runtime>(4)?;
279
6
		let referendum_index = ReferendumCount::<Runtime>::get();
280

            
281
5
		let proposal_origin = Self::track_id_to_origin(
282
6
			track_id
283
6
				.try_into()
284
6
				.map_err(|_| RevertReason::value_is_too_large("Track id type").into())
285
6
				.in_field("trackId")?,
286
1
		)?;
287
5
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
288
5

            
289
5
		let call = ReferendaCall::<Runtime>::submit {
290
5
			proposal_origin,
291
5
			proposal,
292
5
			enactment_moment,
293
5
		}
294
5
		.into();
295
5

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

            
298
5
		Ok(referendum_index)
299
6
	}
300

            
301
	#[precompile::public("referendumStatus(uint32)")]
302
	#[precompile::view]
303
	fn referendum_status(
304
		handle: &mut impl PrecompileHandle,
305
		referendum_index: u32,
306
	) -> EvmResult<u8> {
307
		// ReferendumInfoFor: Blake2128(16) + 4 + ReferendumInfoOf::max_encoded_len
308
		handle.record_db_read::<Runtime>(
309
			20 + pallet_referenda::ReferendumInfoOf::<Runtime, ()>::max_encoded_len(),
310
		)?;
311

            
312
		let status = match ReferendumInfoFor::<Runtime>::get(referendum_index).ok_or(
313
			RevertReason::custom("Referendum does not exist for index")
314
				.in_field("referendum_index"),
315
		)? {
316
			ReferendumInfo::Ongoing(..) => 0,
317
			ReferendumInfo::Approved(..) => 1,
318
			ReferendumInfo::Rejected(..) => 2,
319
			ReferendumInfo::Cancelled(..) => 3,
320
			ReferendumInfo::TimedOut(..) => 4,
321
			ReferendumInfo::Killed(..) => 5,
322
		};
323

            
324
		Ok(status)
325
	}
326

            
327
	#[precompile::public("ongoingReferendumInfo(uint32)")]
328
	#[precompile::view]
329
	fn ongoing_referendum_info(
330
		handle: &mut impl PrecompileHandle,
331
		referendum_index: u32,
332
	) -> EvmResult<OngoingReferendumInfo> {
333
		// ReferendumInfoFor: Blake2128(16) + 4 + ReferendumInfoOf::max_encoded_len
334
		handle.record_db_read::<Runtime>(
335
			20 + pallet_referenda::ReferendumInfoOf::<Runtime, ()>::max_encoded_len(),
336
		)?;
337

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

            
373
				Ok(OngoingReferendumInfo {
374
					track_id,
375
					origin: info.origin.encode().into(),
376
					proposal: info.proposal.encode().into(),
377
					enactment_type,
378
					enactment_time,
379
					submission_time: info.submitted.into(),
380
					submission_depositor: Address(info.submission_deposit.who.into()),
381
					submission_deposit: info.submission_deposit.amount.into(),
382
					decision_depositor,
383
					decision_deposit,
384
					deciding_since,
385
					deciding_confirming_end,
386
					ayes: info.tally.ayes(info.track).into(),
387
					support: info.tally.support(info.track).deconstruct(),
388
					approval: info.tally.approval(info.track).deconstruct(),
389
					in_queue: info.in_queue,
390
					alarm_time,
391
					alarm_task_address,
392
				})
393
			}
394
			_ => Err(RevertReason::custom("Referendum not ongoing").into()),
395
		}
396
	}
397

            
398
	#[precompile::public("closedReferendumInfo(uint32)")]
399
	#[precompile::view]
400
	fn closed_referendum_info(
401
		handle: &mut impl PrecompileHandle,
402
		referendum_index: u32,
403
	) -> EvmResult<ClosedReferendumInfo> {
404
		// ReferendumInfoFor: Blake2128(16) + 4 + ReferendumInfoOf::max_encoded_len
405
		handle.record_db_read::<Runtime>(
406
			20 + pallet_referenda::ReferendumInfoOf::<Runtime, ()>::max_encoded_len(),
407
		)?;
408

            
409
		let get_closed_ref_info =
410
			|status,
411
			 moment: BlockNumberFor<Runtime>,
412
			 submission_deposit: Option<Deposit<Runtime::AccountId, BalanceOf<Runtime>>>,
413
			 decision_deposit: Option<Deposit<Runtime::AccountId, BalanceOf<Runtime>>>|
414
			 -> ClosedReferendumInfo {
415
				let (submission_depositor, submission_deposit_amount): (Address, U256) =
416
					if let Some(Deposit { who, amount }) = submission_deposit {
417
						(Address(who.into()), amount.into())
418
					} else {
419
						(Address(H160::zero()), U256::zero())
420
					};
421
				let (decision_depositor, decision_deposit_amount) =
422
					if let Some(Deposit { who, amount }) = decision_deposit {
423
						(Address(who.into()), amount.into())
424
					} else {
425
						(Address(H160::zero()), U256::zero())
426
					};
427
				ClosedReferendumInfo {
428
					status,
429
					end: moment.into(),
430
					submission_depositor,
431
					submission_deposit: submission_deposit_amount,
432
					decision_depositor,
433
					decision_deposit: decision_deposit_amount,
434
				}
435
			};
436

            
437
		match ReferendumInfoFor::<Runtime>::get(referendum_index).ok_or(
438
			RevertReason::custom("Referendum does not exist for index")
439
				.in_field("referendum_index"),
440
		)? {
441
			ReferendumInfo::Approved(moment, submission_deposit, decision_deposit) => Ok(
442
				get_closed_ref_info(1, moment, submission_deposit, decision_deposit),
443
			),
444
			ReferendumInfo::Rejected(moment, submission_deposit, decision_deposit) => Ok(
445
				get_closed_ref_info(2, moment, submission_deposit, decision_deposit),
446
			),
447
			ReferendumInfo::Cancelled(moment, submission_deposit, decision_deposit) => Ok(
448
				get_closed_ref_info(3, moment, submission_deposit, decision_deposit),
449
			),
450
			ReferendumInfo::TimedOut(moment, submission_deposit, decision_deposit) => Ok(
451
				get_closed_ref_info(4, moment, submission_deposit, decision_deposit),
452
			),
453
			_ => Err(RevertReason::custom("Referendum not closed").into()),
454
		}
455
	}
456

            
457
	#[precompile::public("killedReferendumBlock(uint32)")]
458
	#[precompile::view]
459
	fn killed_referendum_block(
460
		handle: &mut impl PrecompileHandle,
461
		referendum_index: u32,
462
	) -> EvmResult<U256> {
463
		// ReferendumInfoFor: Blake2128(16) + 4 + ReferendumInfoOf::max_encoded_len
464
		handle.record_db_read::<Runtime>(
465
			20 + pallet_referenda::ReferendumInfoOf::<Runtime, ()>::max_encoded_len(),
466
		)?;
467

            
468
		let block = match ReferendumInfoFor::<Runtime>::get(referendum_index).ok_or(
469
			RevertReason::custom("Referendum does not exist for index")
470
				.in_field("referendum_index"),
471
		)? {
472
			ReferendumInfo::Killed(b) => b,
473
			_ => return Err(RevertReason::custom("Referendum not killed").into()),
474
		};
475

            
476
		Ok(block.into())
477
	}
478

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

            
500
4
		let referendum_index = Self::submit(
501
4
			handle,
502
4
			track_id,
503
4
			proposal,
504
4
			DispatchTime::At(block_number.into()),
505
4
		)?;
506
3
		let event = log2(
507
3
			handle.context().address,
508
3
			SELECTOR_LOG_SUBMITTED_AT,
509
3
			H256::from_low_u64_be(track_id as u64),
510
3
			solidity::encode_event_data((referendum_index, proposal_hash)),
511
3
		);
512
3
		event.record(handle)?;
513

            
514
3
		Ok(referendum_index)
515
4
	}
516

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

            
538
2
		let referendum_index = Self::submit(
539
2
			handle,
540
2
			track_id,
541
2
			proposal,
542
2
			DispatchTime::After(block_number.into()),
543
2
		)?;
544
2
		let event = log2(
545
2
			handle.context().address,
546
2
			SELECTOR_LOG_SUBMITTED_AFTER,
547
2
			H256::from_low_u64_be(track_id as u64),
548
2
			solidity::encode_event_data((referendum_index, proposal_hash)),
549
2
		);
550
2

            
551
2
		event.record(handle)?;
552

            
553
2
		Ok(referendum_index)
554
2
	}
555

            
556
	/// Post the Decision Deposit for a referendum.
557
	///
558
	/// Parameters:
559
	/// * index: The index of the submitted referendum whose Decision Deposit is yet to be posted.
560
	#[precompile::public("placeDecisionDeposit(uint32)")]
561
1
	fn place_decision_deposit(handle: &mut impl PrecompileHandle, index: u32) -> EvmResult {
562
1
		// ReferendumInfoFor: Blake2128(16) + 4 + ReferendumInfoOf::max_encoded_len
563
1
		handle.record_db_read::<Runtime>(
564
1
			20 + pallet_referenda::ReferendumInfoOf::<Runtime, ()>::max_encoded_len(),
565
1
		)?;
566
1
		handle.record_log_costs_manual(1, 32 * 3)?;
567

            
568
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
569
1

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

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

            
574
		// Once the deposit has been succesfully placed, it is available in the ReferendumStatus.
575
1
		let ongoing_referendum = Referenda::<Runtime>::ensure_ongoing(index).map_err(|_| {
576
			RevertReason::custom("Provided index is not an ongoing referendum").in_field("index")
577
1
		})?;
578
1
		let decision_deposit: U256 =
579
1
			if let Some(decision_deposit) = ongoing_referendum.decision_deposit {
580
1
				decision_deposit.amount.into()
581
			} else {
582
				U256::zero()
583
			};
584
1
		let event = log1(
585
1
			handle.context().address,
586
1
			SELECTOR_LOG_DECISION_DEPOSIT_PLACED,
587
1
			solidity::encode_event_data((
588
1
				index,
589
1
				Address(handle.context().caller),
590
1
				decision_deposit,
591
1
			)),
592
1
		);
593
1

            
594
1
		event.record(handle)?;
595
1
		Ok(())
596
1
	}
597

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

            
621
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
622
1

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

            
625
1
		<RuntimeHelper<Runtime>>::try_dispatch(handle, Some(origin).into(), call, 0)?;
626
1
		let event = log1(
627
1
			handle.context().address,
628
1
			SELECTOR_LOG_DECISION_DEPOSIT_REFUNDED,
629
1
			solidity::encode_event_data((index, Address(who), refunded_deposit)),
630
1
		);
631
1

            
632
1
		event.record(handle)?;
633
1
		Ok(())
634
1
	}
635

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

            
657
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
658
1

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

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

            
663
1
		let event = log1(
664
1
			handle.context().address,
665
1
			SELECTOR_LOG_SUBMISSION_DEPOSIT_REFUNDED,
666
1
			solidity::encode_event_data((index, Address(who), refunded_deposit)),
667
1
		);
668
1

            
669
1
		event.record(handle)?;
670

            
671
1
		Ok(())
672
1
	}
673
}