1
// Copyright 2019-2022 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
{
158
	// The accessors are first. They directly return their result.
159
	#[precompile::public("referendumCount()")]
160
	#[precompile::view]
161
	fn referendum_count(handle: &mut impl PrecompileHandle) -> EvmResult<u32> {
162
		// ReferendumCount
163
		handle.record_db_read::<Runtime>(4)?;
164
		let ref_count = ReferendumCount::<Runtime>::get();
165
		log::trace!(target: "referendum-precompile", "Referendum count is {:?}", ref_count);
166

            
167
		Ok(ref_count)
168
	}
169

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

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

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

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

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

            
213
		Ok(track_ids)
214
	}
215

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

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

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

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

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

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

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

            
297
5
		Ok(referendum_index)
298
6
	}
299

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

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

            
323
		Ok(status)
324
	}
325

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

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

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

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

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

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

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

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

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

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

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

            
513
3
		Ok(referendum_index)
514
4
	}
515

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

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

            
550
2
		event.record(handle)?;
551

            
552
2
		Ok(referendum_index)
553
2
	}
554

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
668
1
		event.record(handle)?;
669

            
670
1
		Ok(())
671
1
	}
672
}