1#![cfg_attr(not(feature = "std"), no_std)]
18
19use account::SYSTEM_ACCOUNT_SIZE;
20use fp_evm::PrecompileHandle;
21use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo};
22use frame_support::traits::{Currency, Polling};
23use pallet_conviction_voting::Call as ConvictionVotingCall;
24use pallet_conviction_voting::{
25 AccountVote, Casting, ClassLocksFor, Conviction, Delegating, Tally, TallyOf, Vote, Voting,
26 VotingFor,
27};
28use pallet_evm::{AddressMapping, Log};
29use precompile_utils::prelude::*;
30use sp_core::{Get, MaxEncodedLen, H160, H256, U256};
31use sp_runtime::traits::{Dispatchable, StaticLookup};
32use sp_std::marker::PhantomData;
33use sp_std::vec::Vec;
34
35#[cfg(test)]
36mod mock;
37#[cfg(test)]
38mod tests;
39
40type BalanceOf<Runtime> = <<Runtime as pallet_conviction_voting::Config>::Currency as Currency<
41 <Runtime as frame_system::Config>::AccountId,
42>>::Balance;
43type IndexOf<Runtime> = <<Runtime as pallet_conviction_voting::Config>::Polls as Polling<
44 Tally<
45 <<Runtime as pallet_conviction_voting::Config>::Currency as Currency<
46 <Runtime as frame_system::Config>::AccountId,
47 >>::Balance,
48 <Runtime as pallet_conviction_voting::Config>::MaxTurnout,
49 >,
50>>::Index;
51type ClassOf<Runtime> = <<Runtime as pallet_conviction_voting::Config>::Polls as Polling<
52 Tally<
53 <<Runtime as pallet_conviction_voting::Config>::Currency as Currency<
54 <Runtime as frame_system::Config>::AccountId,
55 >>::Balance,
56 <Runtime as pallet_conviction_voting::Config>::MaxTurnout,
57 >,
58>>::Class;
59type VotingOf<Runtime, Instance = ()> = Voting<
60 BalanceOf<Runtime>,
61 <Runtime as frame_system::Config>::AccountId,
62 pallet_conviction_voting::BlockNumberFor<Runtime, Instance>,
63 <<Runtime as pallet_conviction_voting::Config>::Polls as Polling<TallyOf<Runtime>>>::Index,
64 <Runtime as pallet_conviction_voting::Config>::MaxVotes,
65>;
66
67pub(crate) const SELECTOR_LOG_VOTED: [u8; 32] =
69 keccak256!("Voted(uint32,address,bool,uint256,uint8)");
70
71pub(crate) const SELECTOR_LOG_VOTE_SPLIT: [u8; 32] =
73 keccak256!("VoteSplit(uint32,address,uint256,uint256)");
74
75pub(crate) const SELECTOR_LOG_VOTE_SPLIT_ABSTAINED: [u8; 32] =
77 keccak256!("VoteSplitAbstained(uint32,address,uint256,uint256,uint256)");
78
79pub(crate) const SELECTOR_LOG_VOTE_REMOVED: [u8; 32] = keccak256!("VoteRemoved(uint32,address)");
81
82pub(crate) const SELECTOR_LOG_VOTE_REMOVED_FOR_TRACK: [u8; 32] =
84 keccak256!("VoteRemovedForTrack(uint32,uint16,address)");
85
86pub(crate) const SELECTOR_LOG_VOTE_REMOVED_OTHER: [u8; 32] =
88 keccak256!("VoteRemovedOther(uint32,address,address,uint16)");
89
90pub(crate) const SELECTOR_LOG_DELEGATED: [u8; 32] =
92 keccak256!("Delegated(uint16,address,address,uint256,uint8)");
93
94pub(crate) const SELECTOR_LOG_UNDELEGATED: [u8; 32] = keccak256!("Undelegated(uint16,address)");
96
97pub(crate) const SELECTOR_LOG_UNLOCKED: [u8; 32] = keccak256!("Unlocked(uint16,address)");
99
100pub struct ConvictionVotingPrecompile<Runtime>(PhantomData<Runtime>);
102
103#[precompile_utils::precompile]
104impl<Runtime> ConvictionVotingPrecompile<Runtime>
105where
106 Runtime: pallet_conviction_voting::Config + pallet_evm::Config + frame_system::Config,
107 BalanceOf<Runtime>: TryFrom<U256> + Into<U256>,
108 <Runtime as frame_system::Config>::RuntimeCall:
109 Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
110 <<Runtime as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin:
111 From<Option<Runtime::AccountId>>,
112 Runtime::AccountId: Into<H160>,
113 <Runtime as frame_system::Config>::RuntimeCall: From<ConvictionVotingCall<Runtime>>,
114 IndexOf<Runtime>: TryFrom<u32> + TryInto<u32>,
115 ClassOf<Runtime>: TryFrom<u16> + TryInto<u16>,
116 <Runtime as pallet_conviction_voting::Config>::Polls: Polling<
117 Tally<
118 <<Runtime as pallet_conviction_voting::Config>::Currency as Currency<
119 <Runtime as frame_system::Config>::AccountId,
120 >>::Balance,
121 <Runtime as pallet_conviction_voting::Config>::MaxTurnout,
122 >,
123 >,
124 <Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
125{
126 fn vote(
128 handle: &mut impl PrecompileHandle,
129 poll_index: u32,
130 vote: AccountVote<U256>,
131 ) -> EvmResult {
132 let caller = handle.context().caller;
133 let (poll_index, vote, event) = Self::log_vote_event(handle, poll_index, vote)?;
134
135 let origin = Runtime::AddressMapping::into_account_id(caller);
136 let call = ConvictionVotingCall::<Runtime>::vote { poll_index, vote }.into();
137
138 <RuntimeHelper<Runtime>>::try_dispatch(handle, Some(origin).into(), call, 0)?;
139
140 event.record(handle)?;
141
142 Ok(())
143 }
144
145 #[precompile::public("voteYes(uint32,uint256,uint8)")]
152 fn vote_yes(
153 handle: &mut impl PrecompileHandle,
154 poll_index: u32,
155 vote_amount: U256,
156 conviction: u8,
157 ) -> EvmResult {
158 Self::vote(
159 handle,
160 poll_index,
161 AccountVote::Standard {
162 vote: Vote {
163 aye: true,
164 conviction: Self::u8_to_conviction(conviction).in_field("conviction")?,
165 },
166 balance: vote_amount,
167 },
168 )
169 }
170
171 #[precompile::public("voteNo(uint32,uint256,uint8)")]
178 fn vote_no(
179 handle: &mut impl PrecompileHandle,
180 poll_index: u32,
181 vote_amount: U256,
182 conviction: u8,
183 ) -> EvmResult {
184 Self::vote(
185 handle,
186 poll_index,
187 AccountVote::Standard {
188 vote: Vote {
189 aye: false,
190 conviction: Self::u8_to_conviction(conviction).in_field("conviction")?,
191 },
192 balance: vote_amount,
193 },
194 )
195 }
196
197 #[precompile::public("voteSplit(uint32,uint256,uint256)")]
204 fn vote_split(
205 handle: &mut impl PrecompileHandle,
206 poll_index: u32,
207 aye: U256,
208 nay: U256,
209 ) -> EvmResult {
210 Self::vote(handle, poll_index, AccountVote::Split { aye, nay })
211 }
212
213 #[precompile::public("voteSplitAbstain(uint32,uint256,uint256,uint256)")]
220 fn vote_split_abstain(
221 handle: &mut impl PrecompileHandle,
222 poll_index: u32,
223 aye: U256,
224 nay: U256,
225 abstain: U256,
226 ) -> EvmResult {
227 Self::vote(
228 handle,
229 poll_index,
230 AccountVote::SplitAbstain { aye, nay, abstain },
231 )
232 }
233
234 #[precompile::public("removeVote(uint32)")]
235 fn remove_vote(handle: &mut impl PrecompileHandle, poll_index: u32) -> EvmResult {
236 Self::rm_vote(handle, poll_index, None)
237 }
238
239 #[precompile::public("removeVoteForTrack(uint32,uint16)")]
240 fn remove_vote_for_track(
241 handle: &mut impl PrecompileHandle,
242 poll_index: u32,
243 track_id: u16,
244 ) -> EvmResult {
245 Self::rm_vote(handle, poll_index, Some(track_id))
246 }
247
248 fn rm_vote(
250 handle: &mut impl PrecompileHandle,
251 poll_index: u32,
252 maybe_track_id: Option<u16>,
253 ) -> EvmResult {
254 let caller = handle.context().caller;
255 let index = Self::u32_to_index(poll_index).in_field("pollIndex")?;
256 let (event, class) = if let Some(track_id) = maybe_track_id {
257 log::trace!(
258 target: "conviction-voting-precompile",
259 "Removing vote from poll {:?} for track {:?}",
260 index,
261 track_id,
262 );
263 (
264 log2(
265 handle.context().address,
266 SELECTOR_LOG_VOTE_REMOVED_FOR_TRACK,
267 H256::from_low_u64_be(poll_index as u64),
268 solidity::encode_event_data((track_id, Address(caller))),
269 ),
270 Some(Self::u16_to_track_id(track_id).in_field("trackId")?),
271 )
272 } else {
273 log::trace!(
274 target: "conviction-voting-precompile",
275 "Removing vote from poll {:?}",
276 index,
277 );
278 (
279 log2(
280 handle.context().address,
281 SELECTOR_LOG_VOTE_REMOVED,
282 H256::from_low_u64_be(poll_index as u64),
283 solidity::encode_event_data(Address(caller)),
284 ),
285 None,
286 )
287 };
288
289 let origin = Runtime::AddressMapping::into_account_id(caller);
290 let call = ConvictionVotingCall::<Runtime>::remove_vote { class, index };
291
292 RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
293
294 event.record(handle)?;
295
296 Ok(())
297 }
298
299 #[precompile::public("removeOtherVote(address,uint16,uint32)")]
300 fn remove_other_vote(
301 handle: &mut impl PrecompileHandle,
302 target: Address,
303 track_id: u16,
304 poll_index: u32,
305 ) -> EvmResult {
306 let caller = handle.context().caller;
307
308 let event = log2(
309 handle.context().address,
310 SELECTOR_LOG_VOTE_REMOVED_OTHER,
311 H256::from_low_u64_be(poll_index as u64), solidity::encode_event_data((Address(caller), target, track_id)),
313 );
314 handle.record_log_costs(&[&event])?;
315
316 let class = Self::u16_to_track_id(track_id).in_field("trackId")?;
317 let index = Self::u32_to_index(poll_index).in_field("pollIndex")?;
318
319 let target = Runtime::AddressMapping::into_account_id(target.into());
320 let target: <Runtime::Lookup as StaticLookup>::Source =
321 Runtime::Lookup::unlookup(target.clone());
322
323 log::trace!(
324 target: "conviction-voting-precompile",
325 "Removing other vote from poll {:?}",
326 index
327 );
328
329 let origin = Runtime::AddressMapping::into_account_id(caller);
330 let call = ConvictionVotingCall::<Runtime>::remove_other_vote {
331 target,
332 class,
333 index,
334 };
335
336 RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
337
338 event.record(handle)?;
339
340 Ok(())
341 }
342
343 #[precompile::public("delegate(uint16,address,uint8,uint256)")]
344 fn delegate(
345 handle: &mut impl PrecompileHandle,
346 track_id: u16,
347 representative: Address,
348 conviction: u8,
349 amount: U256,
350 ) -> EvmResult {
351 let caller = handle.context().caller;
352
353 let event = log2(
354 handle.context().address,
355 SELECTOR_LOG_DELEGATED,
356 H256::from_low_u64_be(track_id as u64), solidity::encode_event_data((Address(caller), representative, amount, conviction)),
358 );
359 handle.record_log_costs(&[&event])?;
360
361 let class = Self::u16_to_track_id(track_id).in_field("trackId")?;
362 let amount = Self::u256_to_amount(amount).in_field("amount")?;
363 let conviction = Self::u8_to_conviction(conviction).in_field("conviction")?;
364
365 log::trace!(target: "conviction-voting-precompile",
366 "Delegating vote to {:?} with balance {:?} and conviction {:?}",
367 representative, amount, conviction
368 );
369
370 let representative = Runtime::AddressMapping::into_account_id(representative.into());
371 let to: <Runtime::Lookup as StaticLookup>::Source =
372 Runtime::Lookup::unlookup(representative.clone());
373 let origin = Runtime::AddressMapping::into_account_id(caller);
374 let call = ConvictionVotingCall::<Runtime>::delegate {
375 class,
376 to,
377 conviction,
378 balance: amount,
379 };
380
381 RuntimeHelper::<Runtime>::try_dispatch(
382 handle,
383 Some(origin).into(),
384 call,
385 SYSTEM_ACCOUNT_SIZE,
386 )?;
387
388 event.record(handle)?;
389
390 Ok(())
391 }
392
393 #[precompile::public("undelegate(uint16)")]
394 fn undelegate(handle: &mut impl PrecompileHandle, track_id: u16) -> EvmResult {
395 let caller = handle.context().caller;
396
397 let event = log2(
398 handle.context().address,
399 SELECTOR_LOG_UNDELEGATED,
400 H256::from_low_u64_be(track_id as u64), solidity::encode_event_data(Address(caller)),
402 );
403 handle.record_log_costs(&[&event])?;
404
405 let class = Self::u16_to_track_id(track_id).in_field("trackId")?;
406 let origin = Runtime::AddressMapping::into_account_id(caller);
407 let call = ConvictionVotingCall::<Runtime>::undelegate { class };
408
409 RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
410
411 event.record(handle)?;
412
413 Ok(())
414 }
415
416 #[precompile::public("unlock(uint16,address)")]
417 fn unlock(handle: &mut impl PrecompileHandle, track_id: u16, target: Address) -> EvmResult {
418 let class = Self::u16_to_track_id(track_id).in_field("trackId")?;
419
420 let event = log2(
421 handle.context().address,
422 SELECTOR_LOG_UNLOCKED,
423 H256::from_low_u64_be(track_id as u64), solidity::encode_event_data(target),
425 );
426 handle.record_log_costs(&[&event])?;
427
428 let target: H160 = target.into();
429 let target = Runtime::AddressMapping::into_account_id(target);
430 let target: <Runtime::Lookup as StaticLookup>::Source =
431 Runtime::Lookup::unlookup(target.clone());
432
433 log::trace!(
434 target: "conviction-voting-precompile",
435 "Unlocking conviction-voting tokens for {:?}", target
436 );
437
438 let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
439 let call = ConvictionVotingCall::<Runtime>::unlock { class, target };
440
441 RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
442
443 event.record(handle)?;
444
445 Ok(())
446 }
447
448 #[precompile::public("votingFor(address,uint16)")]
449 #[precompile::view]
450 fn voting_for(
451 handle: &mut impl PrecompileHandle,
452 who: Address,
453 track_id: u16,
454 ) -> EvmResult<OutputVotingFor> {
455 handle.record_db_read::<Runtime>(38 + VotingOf::<Runtime>::max_encoded_len())?;
457
458 let who = Runtime::AddressMapping::into_account_id(who.into());
459 let class = Self::u16_to_track_id(track_id).in_field("trackId")?;
460
461 let voting = <VotingFor<Runtime>>::get(&who, &class);
462
463 Ok(Self::voting_to_output(voting)?)
464 }
465
466 #[precompile::public("classLocksFor(address)")]
467 #[precompile::view]
468 fn class_locks_for(
469 handle: &mut impl PrecompileHandle,
470 who: Address,
471 ) -> EvmResult<Vec<OutputClassLock>> {
472 handle.record_db_read::<Runtime>(
474 28 + ((2 * frame_support::traits::ClassCountOf::<
475 <Runtime as pallet_conviction_voting::Config>::Polls,
476 Tally<
477 <<Runtime as pallet_conviction_voting::Config>::Currency as Currency<
478 <Runtime as frame_system::Config>::AccountId,
479 >>::Balance,
480 <Runtime as pallet_conviction_voting::Config>::MaxTurnout,
481 >,
482 >::get()) as usize),
483 )?;
484
485 let who = Runtime::AddressMapping::into_account_id(who.into());
486
487 let class_locks_for = <ClassLocksFor<Runtime>>::get(&who);
488 let mut output = Vec::new();
489 for (track_id, amount) in class_locks_for {
490 output.push(OutputClassLock {
491 track: Self::track_id_to_u16(track_id)?,
492 amount: amount.into(),
493 });
494 }
495
496 Ok(output)
497 }
498
499 fn u8_to_conviction(conviction: u8) -> MayRevert<Conviction> {
500 conviction
501 .try_into()
502 .map_err(|_| RevertReason::custom("Must be an integer between 0 and 6 included").into())
503 }
504
505 fn u32_to_index(index: u32) -> MayRevert<IndexOf<Runtime>> {
506 index
507 .try_into()
508 .map_err(|_| RevertReason::value_is_too_large("index type").into())
509 }
510
511 fn u16_to_track_id(class: u16) -> MayRevert<ClassOf<Runtime>> {
512 class
513 .try_into()
514 .map_err(|_| RevertReason::value_is_too_large("trackId type").into())
515 }
516
517 fn track_id_to_u16(class: ClassOf<Runtime>) -> MayRevert<u16> {
518 class
519 .try_into()
520 .map_err(|_| RevertReason::value_is_too_large("trackId type").into())
521 }
522
523 fn u256_to_amount(value: U256) -> MayRevert<BalanceOf<Runtime>> {
524 value
525 .try_into()
526 .map_err(|_| RevertReason::value_is_too_large("balance type").into())
527 }
528
529 fn log_vote_event(
530 handle: &mut impl PrecompileHandle,
531 poll_index: u32,
532 vote: AccountVote<U256>,
533 ) -> EvmResult<(IndexOf<Runtime>, AccountVote<BalanceOf<Runtime>>, Log)> {
534 let (contract_addr, caller) = (handle.context().address, handle.context().caller);
535 let (vote, event) = match vote {
536 AccountVote::Standard { vote, balance } => {
537 let event = log2(
538 contract_addr,
539 SELECTOR_LOG_VOTED,
540 H256::from_low_u64_be(poll_index as u64),
541 solidity::encode_event_data((
542 Address(caller),
543 vote.aye,
544 balance,
545 u8::from(vote.conviction),
546 )),
547 );
548 (
549 AccountVote::Standard {
550 vote,
551 balance: Self::u256_to_amount(balance).in_field("voteAmount")?,
552 },
553 event,
554 )
555 }
556 AccountVote::Split { aye, nay } => {
557 let event = log2(
558 contract_addr,
559 SELECTOR_LOG_VOTE_SPLIT,
560 H256::from_low_u64_be(poll_index as u64),
561 solidity::encode_event_data((Address(caller), aye, nay)),
562 );
563 (
564 AccountVote::Split {
565 aye: Self::u256_to_amount(aye).in_field("aye")?,
566 nay: Self::u256_to_amount(nay).in_field("nay")?,
567 },
568 event,
569 )
570 }
571 AccountVote::SplitAbstain { aye, nay, abstain } => {
572 let event = log2(
573 contract_addr,
574 SELECTOR_LOG_VOTE_SPLIT_ABSTAINED,
575 H256::from_low_u64_be(poll_index as u64),
576 solidity::encode_event_data((Address(caller), aye, nay, abstain)),
577 );
578 (
579 AccountVote::SplitAbstain {
580 aye: Self::u256_to_amount(aye).in_field("aye")?,
581 nay: Self::u256_to_amount(nay).in_field("nay")?,
582 abstain: Self::u256_to_amount(abstain).in_field("abstain")?,
583 },
584 event,
585 )
586 }
587 };
588 handle.record_log_costs(&[&event])?;
589 Ok((Self::u32_to_index(poll_index)?, vote, event))
590 }
591
592 fn voting_to_output(voting: VotingOf<Runtime>) -> MayRevert<OutputVotingFor> {
593 let output = match voting {
594 Voting::Casting(Casting {
595 votes,
596 delegations,
597 prior,
598 }) => {
599 let mut output_votes = Vec::new();
600 for (poll_index, account_vote) in votes {
601 let poll_index: u32 = poll_index
602 .try_into()
603 .map_err(|_| RevertReason::value_is_too_large("index type"))?;
604 let account_vote = match account_vote {
605 AccountVote::Standard { vote, balance } => OutputAccountVote {
606 is_standard: true,
607 standard: StandardVote {
608 vote: OutputVote {
609 aye: vote.aye,
610 conviction: vote.conviction.into(),
611 },
612 balance: balance.into(),
613 },
614 ..Default::default()
615 },
616 AccountVote::Split { aye, nay } => OutputAccountVote {
617 is_split: true,
618 split: SplitVote {
619 aye: aye.into(),
620 nay: nay.into(),
621 },
622 ..Default::default()
623 },
624 AccountVote::SplitAbstain { aye, nay, abstain } => OutputAccountVote {
625 is_split_abstain: true,
626 split_abstain: SplitAbstainVote {
627 aye: aye.into(),
628 nay: nay.into(),
629 abstain: abstain.into(),
630 },
631 ..Default::default()
632 },
633 };
634
635 output_votes.push(PollAccountVote {
636 poll_index,
637 account_vote,
638 });
639 }
640
641 OutputVotingFor {
642 is_casting: true,
643 casting: OutputCasting {
644 votes: output_votes,
645 delegations: Delegations {
646 votes: delegations.votes.into(),
647 capital: delegations.capital.into(),
648 },
649 prior: PriorLock {
650 balance: prior.locked().into(),
651 },
652 },
653 ..Default::default()
654 }
655 }
656 Voting::Delegating(Delegating {
657 balance,
658 target,
659 conviction,
660 delegations,
661 prior,
662 }) => OutputVotingFor {
663 is_delegating: true,
664 delegating: OutputDelegating {
665 balance: balance.into(),
666 target: Address(target.into()),
667 conviction: conviction.into(),
668 delegations: Delegations {
669 votes: delegations.votes.into(),
670 capital: delegations.capital.into(),
671 },
672 prior: PriorLock {
673 balance: prior.locked().into(),
674 },
675 },
676 ..Default::default()
677 },
678 };
679
680 Ok(output)
681 }
682}
683
684#[derive(Default, solidity::Codec)]
685pub struct OutputClassLock {
686 track: u16,
687 amount: U256,
688}
689
690#[derive(Default, solidity::Codec)]
691pub struct OutputVotingFor {
692 is_casting: bool,
693 is_delegating: bool,
694 casting: OutputCasting,
695 delegating: OutputDelegating,
696}
697
698#[derive(Default, solidity::Codec)]
699pub struct OutputCasting {
700 votes: Vec<PollAccountVote>,
701 delegations: Delegations,
702 prior: PriorLock,
703}
704
705#[derive(Default, solidity::Codec)]
706pub struct PollAccountVote {
707 poll_index: u32,
708 account_vote: OutputAccountVote,
709}
710
711#[derive(Default, solidity::Codec)]
712pub struct OutputDelegating {
713 balance: U256,
714 target: Address,
715 conviction: u8,
716 delegations: Delegations,
717 prior: PriorLock,
718}
719
720#[derive(Default, solidity::Codec)]
721pub struct OutputAccountVote {
722 is_standard: bool,
723 is_split: bool,
724 is_split_abstain: bool,
725 standard: StandardVote,
726 split: SplitVote,
727 split_abstain: SplitAbstainVote,
728}
729
730#[derive(Default, solidity::Codec)]
731pub struct StandardVote {
732 vote: OutputVote,
733 balance: U256,
734}
735
736#[derive(Default, solidity::Codec)]
737pub struct OutputVote {
738 aye: bool,
739 conviction: u8,
740}
741
742#[derive(Default, solidity::Codec)]
743pub struct SplitVote {
744 aye: U256,
745 nay: U256,
746}
747
748#[derive(Default, solidity::Codec)]
749pub struct SplitAbstainVote {
750 aye: U256,
751 nay: U256,
752 abstain: U256,
753}
754
755#[derive(Default, solidity::Codec)]
756pub struct Delegations {
757 votes: U256,
758 capital: U256,
759}
760
761#[derive(Default, solidity::Codec)]
762pub struct PriorLock {
763 balance: U256,
764}