pallet_evm_precompile_referenda/
lib.rs

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
19use fp_evm::PrecompileHandle;
20use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo};
21use frame_support::traits::{
22	schedule::DispatchTime, Bounded, Currency, Get, OriginTrait, VoteTally,
23};
24use pallet_evm::AddressMapping;
25use pallet_referenda::{
26	Call as ReferendaCall, DecidingCount, Deposit, Pallet as Referenda, ReferendumCount,
27	ReferendumInfo, ReferendumInfoFor, TracksInfo,
28};
29use parity_scale_codec::{Encode, MaxEncodedLen};
30use precompile_utils::prelude::*;
31use sp_core::{H160, H256, U256};
32use sp_runtime::str_array;
33use sp_runtime::traits::Dispatchable;
34use sp_std::str;
35use sp_std::{boxed::Box, marker::PhantomData, str::FromStr, vec::Vec};
36
37#[cfg(test)]
38mod mock;
39#[cfg(test)]
40mod tests;
41
42pub const CALL_DATA_LIMIT: u32 = 2u32.pow(16);
43
44type BlockNumberFor<T> = pallet_referenda::BlockNumberFor<T, ()>;
45type BalanceOf<Runtime> = <<Runtime as pallet_referenda::Config>::Currency as Currency<
46	<Runtime as frame_system::Config>::AccountId,
47>>::Balance;
48type TrackIdOf<Runtime> = <<Runtime as pallet_referenda::Config>::Tracks as TracksInfo<
49	BalanceOf<Runtime>,
50	BlockNumberFor<Runtime>,
51>>::Id;
52type BoundedCallOf<Runtime> = Bounded<
53	<Runtime as pallet_referenda::Config>::RuntimeCall,
54	<Runtime as frame_system::Config>::Hashing,
55>;
56
57type OriginOf<Runtime> =
58	<<Runtime as frame_system::Config>::RuntimeOrigin as OriginTrait>::PalletsOrigin;
59
60pub(crate) const SELECTOR_LOG_SUBMITTED_AT: [u8; 32] =
61	keccak256!("SubmittedAt(uint16,uint32,bytes32)");
62
63pub(crate) const SELECTOR_LOG_SUBMITTED_AFTER: [u8; 32] =
64	keccak256!("SubmittedAfter(uint16,uint32,bytes32)");
65
66pub(crate) const SELECTOR_LOG_DECISION_DEPOSIT_PLACED: [u8; 32] =
67	keccak256!("DecisionDepositPlaced(uint32,address,uint256)");
68
69pub(crate) const SELECTOR_LOG_DECISION_DEPOSIT_REFUNDED: [u8; 32] =
70	keccak256!("DecisionDepositRefunded(uint32,address,uint256)");
71
72pub(crate) const SELECTOR_LOG_SUBMISSION_DEPOSIT_REFUNDED: [u8; 32] =
73	keccak256!("SubmissionDepositRefunded(uint32,address,uint256)");
74
75#[derive(solidity::Codec)]
76pub 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)]
89pub 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)]
127pub 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.
137pub struct ReferendaPrecompile<Runtime, GovOrigin>(PhantomData<(Runtime, GovOrigin)>);
138
139#[precompile_utils::precompile]
140impl<Runtime, GovOrigin> ReferendaPrecompile<Runtime, GovOrigin>
141where
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	fn track_id_to_origin(track_id: TrackIdOf<Runtime>) -> EvmResult<Box<OriginOf<Runtime>>> {
254		let track = Runtime::Tracks::tracks()
255			.find(|track| track.id == track_id)
256			.ok_or(RevertReason::custom("No such track").in_field("trackId"))?;
257		let track_info = &track.info;
258		let origin = if track_info.name == str_array("root") {
259			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		Ok(Box::new(origin))
271	}
272
273	// Helper function for submitAt and submitAfter
274	fn submit(
275		handle: &mut impl PrecompileHandle,
276		track_id: u16,
277		proposal: BoundedCallOf<Runtime>,
278		enactment_moment: DispatchTime<BlockNumberFor<Runtime>>,
279	) -> EvmResult<u32> {
280		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		handle.record_db_read::<Runtime>(4)?;
289		let referendum_index = ReferendumCount::<Runtime>::get();
290
291		let proposal_origin = Self::track_id_to_origin(
292			track_id
293				.try_into()
294				.map_err(|_| RevertReason::value_is_too_large("Track id type").into())
295				.in_field("trackId")?,
296		)?;
297		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
298
299		let call = ReferendaCall::<Runtime>::submit {
300			proposal_origin,
301			proposal,
302			enactment_moment,
303		}
304		.into();
305
306		<RuntimeHelper<Runtime>>::try_dispatch(handle, Some(origin).into(), call, 0)?;
307
308		Ok(referendum_index)
309	}
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	fn submit_at(
498		handle: &mut impl PrecompileHandle,
499		track_id: u16,
500		proposal_hash: H256,
501		proposal_len: u32,
502		block_number: u32,
503	) -> EvmResult<u32> {
504		let proposal: BoundedCallOf<Runtime> = Bounded::Lookup {
505			hash: proposal_hash.into(),
506			len: proposal_len,
507		};
508		handle.record_log_costs_manual(2, 32 * 2)?;
509
510		let referendum_index = Self::submit(
511			handle,
512			track_id,
513			proposal,
514			DispatchTime::At(block_number.into()),
515		)?;
516		let event = log2(
517			handle.context().address,
518			SELECTOR_LOG_SUBMITTED_AT,
519			H256::from_low_u64_be(track_id as u64),
520			solidity::encode_event_data((referendum_index, proposal_hash)),
521		);
522		event.record(handle)?;
523
524		Ok(referendum_index)
525	}
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	fn submit_after(
536		handle: &mut impl PrecompileHandle,
537		track_id: u16,
538		proposal_hash: H256,
539		proposal_len: u32,
540		block_number: u32,
541	) -> EvmResult<u32> {
542		let proposal: BoundedCallOf<Runtime> = Bounded::Lookup {
543			hash: proposal_hash.into(),
544			len: proposal_len,
545		};
546		handle.record_log_costs_manual(2, 32 * 2)?;
547
548		let referendum_index = Self::submit(
549			handle,
550			track_id,
551			proposal,
552			DispatchTime::After(block_number.into()),
553		)?;
554		let event = log2(
555			handle.context().address,
556			SELECTOR_LOG_SUBMITTED_AFTER,
557			H256::from_low_u64_be(track_id as u64),
558			solidity::encode_event_data((referendum_index, proposal_hash)),
559		);
560
561		event.record(handle)?;
562
563		Ok(referendum_index)
564	}
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	fn place_decision_deposit(handle: &mut impl PrecompileHandle, index: u32) -> EvmResult {
572		// ReferendumInfoFor: Blake2128(16) + 4 + ReferendumInfoOf::max_encoded_len
573		handle.record_db_read::<Runtime>(
574			20 + pallet_referenda::ReferendumInfoOf::<Runtime, ()>::max_encoded_len(),
575		)?;
576		handle.record_log_costs_manual(1, 32 * 3)?;
577
578		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
579
580		let call = ReferendaCall::<Runtime>::place_decision_deposit { index }.into();
581
582		<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		let ongoing_referendum = Referenda::<Runtime>::ensure_ongoing(index).map_err(|_| {
586			RevertReason::custom("Provided index is not an ongoing referendum").in_field("index")
587		})?;
588		let decision_deposit: U256 =
589			if let Some(decision_deposit) = ongoing_referendum.decision_deposit {
590				decision_deposit.amount.into()
591			} else {
592				U256::zero()
593			};
594		let event = log1(
595			handle.context().address,
596			SELECTOR_LOG_DECISION_DEPOSIT_PLACED,
597			solidity::encode_event_data((
598				index,
599				Address(handle.context().caller),
600				decision_deposit,
601			)),
602		);
603
604		event.record(handle)?;
605		Ok(())
606	}
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	fn refund_decision_deposit(handle: &mut impl PrecompileHandle, index: u32) -> EvmResult {
614		handle.record_log_costs_manual(1, 32 * 3)?;
615		// ReferendumInfoFor: Blake2128(16) + 4 + ReferendumInfoOf::max_encoded_len
616		handle.record_db_read::<Runtime>(
617			20 + pallet_referenda::ReferendumInfoOf::<Runtime, ()>::max_encoded_len(),
618		)?;
619		let (who, refunded_deposit): (H160, U256) = match ReferendumInfoFor::<Runtime>::get(index)
620			.ok_or(
621			RevertReason::custom("Referendum index does not exist").in_field("index"),
622		)? {
623			ReferendumInfo::Approved(_, _, Some(d))
624			| ReferendumInfo::Rejected(_, _, Some(d))
625			| ReferendumInfo::TimedOut(_, _, Some(d))
626			| 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		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
632
633		let call = ReferendaCall::<Runtime>::refund_decision_deposit { index }.into();
634
635		<RuntimeHelper<Runtime>>::try_dispatch(handle, Some(origin).into(), call, 0)?;
636		let event = log1(
637			handle.context().address,
638			SELECTOR_LOG_DECISION_DEPOSIT_REFUNDED,
639			solidity::encode_event_data((index, Address(who), refunded_deposit)),
640		);
641
642		event.record(handle)?;
643		Ok(())
644	}
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	fn refund_submission_deposit(handle: &mut impl PrecompileHandle, index: u32) -> EvmResult {
652		handle.record_log_costs_manual(1, 32 * 3)?;
653		// ReferendumInfoFor: Blake2128(16) + 4 + ReferendumInfoOf::max_encoded_len
654		handle.record_db_read::<Runtime>(
655			20 + pallet_referenda::ReferendumInfoOf::<Runtime, ()>::max_encoded_len(),
656		)?;
657		let (who, refunded_deposit): (H160, U256) =
658			match ReferendumInfoFor::<Runtime>::get(index)
659				.ok_or(RevertReason::custom("Referendum index does not exist").in_field("index"))?
660			{
661				ReferendumInfo::Approved(_, Some(s), _)
662				| 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		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
668
669		let call = ReferendaCall::<Runtime>::refund_submission_deposit { index }.into();
670
671		<RuntimeHelper<Runtime>>::try_dispatch(handle, Some(origin).into(), call, 0)?;
672
673		let event = log1(
674			handle.context().address,
675			SELECTOR_LOG_SUBMISSION_DEPOSIT_REFUNDED,
676			solidity::encode_event_data((index, Address(who), refunded_deposit)),
677		);
678
679		event.record(handle)?;
680
681		Ok(())
682	}
683}