pallet_evm_precompile_parachain_staking/
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//! Precompile to call parachain-staking runtime methods via the EVM
18
19#![cfg_attr(not(feature = "std"), no_std)]
20
21#[cfg(test)]
22mod mock;
23#[cfg(test)]
24mod tests;
25
26use fp_evm::PrecompileHandle;
27use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo};
28use frame_support::pallet_prelude::MaxEncodedLen;
29use frame_support::sp_runtime::Percent;
30use frame_support::traits::{fungible::Inspect, Get};
31use pallet_evm::AddressMapping;
32use pallet_parachain_staking::ScheduledRequest;
33use precompile_utils::prelude::*;
34use sp_core::{H160, U256};
35use sp_runtime::traits::Dispatchable;
36use sp_std::{convert::TryInto, marker::PhantomData, vec::Vec};
37
38type BalanceOf<Runtime> = <<Runtime as pallet_parachain_staking::Config>::Currency as Inspect<
39	<Runtime as frame_system::Config>::AccountId,
40>>::Balance;
41
42/// A precompile to wrap the functionality from parachain_staking.
43///
44/// EXAMPLE USECASE:
45/// A simple example usecase is a contract that allows donors to donate, and stakes all the funds
46/// toward one fixed address chosen by the deployer.
47/// Such a contract could be deployed by a collator candidate, and the deploy address distributed to
48/// supporters who want to donate toward a perpetual nomination fund.
49pub struct ParachainStakingPrecompile<Runtime>(PhantomData<Runtime>);
50
51#[precompile_utils::precompile]
52impl<Runtime> ParachainStakingPrecompile<Runtime>
53where
54	Runtime: pallet_parachain_staking::Config + pallet_evm::Config,
55	Runtime::AccountId: Into<H160>,
56	Runtime::RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
57	Runtime::RuntimeCall: From<pallet_parachain_staking::Call<Runtime>>,
58	BalanceOf<Runtime>: TryFrom<U256> + Into<U256> + solidity::Codec,
59	<Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
60{
61	// Constants
62	#[precompile::public("minDelegation()")]
63	#[precompile::public("min_delegation()")]
64	#[precompile::view]
65	fn min_delegation(_handle: &mut impl PrecompileHandle) -> EvmResult<u128> {
66		let min_nomination: u128 =
67			<<Runtime as pallet_parachain_staking::Config>::MinDelegation as Get<
68				BalanceOf<Runtime>,
69			>>::get()
70			.try_into()
71			.map_err(|_| revert("Amount is too large for provided balance type"))?;
72
73		Ok(min_nomination)
74	}
75
76	// Storage Getters
77	#[precompile::public("points(uint256)")]
78	#[precompile::view]
79	fn points(handle: &mut impl PrecompileHandle, round: Convert<U256, u32>) -> EvmResult<u32> {
80		let round = round.converted();
81		// AccountsPayable: Twox64Concat(8) + RoundIndex(4) + RewardPoint(4)
82		handle.record_db_read::<Runtime>(16)?;
83		let points: u32 = pallet_parachain_staking::Pallet::<Runtime>::points(round);
84
85		Ok(points)
86	}
87
88	#[precompile::public("awardedPoints(uint32,address)")]
89	#[precompile::view]
90	fn awarded_points(
91		handle: &mut impl PrecompileHandle,
92		round: u32,
93		candidate: Address,
94	) -> EvmResult<u32> {
95		// AccountsPayable: Twox64Concat(8) + RoundIndex(4) + Twox64Concat(8) + AccountId(20)
96		// + RewardPoint(4)
97		handle.record_db_read::<Runtime>(44)?;
98
99		let candidate = Runtime::AddressMapping::into_account_id(candidate.0);
100
101		let points = <pallet_parachain_staking::Pallet<Runtime>>::awarded_pts(&round, &candidate);
102
103		Ok(points)
104	}
105
106	#[precompile::public("candidateCount()")]
107	#[precompile::public("candidate_count()")]
108	#[precompile::view]
109	fn candidate_count(handle: &mut impl PrecompileHandle) -> EvmResult<u32> {
110		// CandidatePool: UnBoundedVec(AccountId(20) + Balance(16))
111		// TODO CandidatePool is unbounded, we account for a theoretical 200 pool.
112		handle.record_db_read::<Runtime>(7200)?;
113		// Fetch info.
114		let candidate_count: u32 = <pallet_parachain_staking::Pallet<Runtime>>::candidate_pool()
115			.0
116			.len() as u32;
117
118		// Build output.
119		Ok(candidate_count)
120	}
121
122	#[precompile::public("round()")]
123	#[precompile::view]
124	fn round(handle: &mut impl PrecompileHandle) -> EvmResult<u32> {
125		// Round: RoundInfo(RoundIndex(4) + BlockNumber(4) + 4)
126		handle.record_db_read::<Runtime>(12)?;
127		let round: u32 = <pallet_parachain_staking::Pallet<Runtime>>::round().current;
128
129		Ok(round)
130	}
131
132	#[precompile::public("candidateDelegationCount(address)")]
133	#[precompile::public("candidate_delegation_count(address)")]
134	#[precompile::view]
135	fn candidate_delegation_count(
136		handle: &mut impl PrecompileHandle,
137		candidate: Address,
138	) -> EvmResult<u32> {
139		let candidate = Runtime::AddressMapping::into_account_id(candidate.0);
140		// CandidateInfo: Twox64Concat(8) + AccountId(20) + CandidateMetadata(105)
141		handle.record_db_read::<Runtime>(133)?;
142		let result = if let Some(state) =
143			<pallet_parachain_staking::Pallet<Runtime>>::candidate_info(&candidate)
144		{
145			let candidate_delegation_count: u32 = state.delegation_count;
146
147			log::trace!(
148				target: "staking-precompile",
149				"Result from pallet is {:?}",
150				candidate_delegation_count
151			);
152			candidate_delegation_count
153		} else {
154			log::trace!(
155				target: "staking-precompile",
156				"Candidate {:?} not found, so delegation count is 0",
157				candidate
158			);
159			0u32
160		};
161
162		Ok(result)
163	}
164
165	#[precompile::public("candidateAutoCompoundingDelegationCount(address)")]
166	#[precompile::view]
167	fn candidate_auto_compounding_delegation_count(
168		handle: &mut impl PrecompileHandle,
169		candidate: Address,
170	) -> EvmResult<u32> {
171		// AutoCompoundingDelegations:
172		// Blake2128(16) + AccountId(20)
173		// + BoundedVec(
174		// 	AutoCompoundConfig * (MaxTopDelegationsPerCandidate + MaxBottomDelegationsPerCandidate)
175		// )
176		handle.record_db_read::<Runtime>(
177			36 + (
178				22 * (<Runtime as pallet_parachain_staking::Config>::MaxTopDelegationsPerCandidate::get()
179				+ <Runtime as pallet_parachain_staking::Config>::MaxBottomDelegationsPerCandidate::get())
180				as usize),
181		)?;
182
183		let candidate = Runtime::AddressMapping::into_account_id(candidate.0);
184
185		let count =
186			<pallet_parachain_staking::Pallet<Runtime>>::auto_compounding_delegations(&candidate)
187				.len() as u32;
188
189		Ok(count)
190	}
191
192	#[precompile::public("delegatorDelegationCount(address)")]
193	#[precompile::public("delegator_delegation_count(address)")]
194	#[precompile::view]
195	fn delegator_delegation_count(
196		handle: &mut impl PrecompileHandle,
197		delegator: Address,
198	) -> EvmResult<u32> {
199		let delegator = Runtime::AddressMapping::into_account_id(delegator.0);
200		// CandidateInfo:
201		// Twox64Concat(8) + AccountId(20) + Delegator(56 + MaxDelegationsPerDelegator)
202		handle.record_db_read::<Runtime>(
203			84 + (<Runtime as pallet_parachain_staking::Config>::MaxDelegationsPerDelegator::get()
204				as usize),
205		)?;
206		let result = if let Some(state) =
207			<pallet_parachain_staking::Pallet<Runtime>>::delegator_state(&delegator)
208		{
209			let delegator_delegation_count: u32 = state.delegations.0.len() as u32;
210
211			log::trace!(
212				target: "staking-precompile",
213				"Result from pallet is {:?}",
214				delegator_delegation_count
215			);
216
217			delegator_delegation_count
218		} else {
219			log::trace!(
220				target: "staking-precompile",
221				"Delegator {:?} not found, so delegation count is 0",
222				delegator
223			);
224			0u32
225		};
226
227		Ok(result)
228	}
229
230	#[precompile::public("selectedCandidates()")]
231	#[precompile::public("selected_candidates()")]
232	#[precompile::view]
233	fn selected_candidates(handle: &mut impl PrecompileHandle) -> EvmResult<Vec<Address>> {
234		// TotalSelected
235		handle.record_db_read::<Runtime>(4)?;
236		let total_selected = pallet_parachain_staking::Pallet::<Runtime>::total_selected();
237		// SelectedCandidates: total_selected * AccountId(20)
238		handle.record_db_read::<Runtime>(20 * (total_selected as usize))?;
239		let selected_candidates: Vec<Address> =
240			pallet_parachain_staking::Pallet::<Runtime>::selected_candidates()
241				.into_iter()
242				.map(|address| Address(address.into()))
243				.collect();
244
245		Ok(selected_candidates)
246	}
247
248	#[precompile::public("delegationAmount(address,address)")]
249	#[precompile::view]
250	fn delegation_amount(
251		handle: &mut impl PrecompileHandle,
252		delegator: Address,
253		candidate: Address,
254	) -> EvmResult<U256> {
255		// DelegatorState:
256		// Twox64Concat(8) + AccountId(20) + Delegator(56 + MaxDelegationsPerDelegator)
257		handle.record_db_read::<Runtime>(
258			84 + (<Runtime as pallet_parachain_staking::Config>::MaxDelegationsPerDelegator::get()
259				as usize),
260		)?;
261		let (candidate, delegator) = (
262			Runtime::AddressMapping::into_account_id(candidate.0),
263			Runtime::AddressMapping::into_account_id(delegator.0),
264		);
265		let amount = pallet_parachain_staking::Pallet::<Runtime>::delegator_state(&delegator)
266			.and_then(|state| {
267				state
268					.delegations
269					.0
270					.into_iter()
271					.find(|b| b.owner == candidate)
272			})
273			.map_or(
274				U256::zero(),
275				|pallet_parachain_staking::Bond { amount, .. }| amount.into(),
276			);
277
278		Ok(amount)
279	}
280
281	// Role Verifiers
282	#[precompile::public("isInTopDelegations(address,address)")]
283	#[precompile::view]
284	fn is_in_top_delegations(
285		handle: &mut impl PrecompileHandle,
286		delegator: Address,
287		candidate: Address,
288	) -> EvmResult<bool> {
289		let (candidate, delegator) = (
290			Runtime::AddressMapping::into_account_id(candidate.0),
291			Runtime::AddressMapping::into_account_id(delegator.0),
292		);
293		// TopDelegations:
294		// Twox64Concat(8) + AccountId(20) + Balance(16)
295		// + (AccountId(20) + Balance(16) * MaxTopDelegationsPerCandidate)
296		handle.record_db_read::<Runtime>(
297			44 + ((36
298				* <Runtime as pallet_parachain_staking::Config>::MaxTopDelegationsPerCandidate::get(
299				)) as usize),
300		)?;
301		let is_in_top_delegations = pallet_parachain_staking::Pallet::<Runtime>::top_delegations(
302			&candidate,
303		)
304		.map_or(false, |delegations| {
305			delegations
306				.delegations
307				.into_iter()
308				.any(|b| b.owner == delegator)
309		});
310
311		Ok(is_in_top_delegations)
312	}
313
314	#[precompile::public("isDelegator(address)")]
315	#[precompile::public("is_delegator(address)")]
316	#[precompile::view]
317	fn is_delegator(handle: &mut impl PrecompileHandle, delegator: Address) -> EvmResult<bool> {
318		let delegator = Runtime::AddressMapping::into_account_id(delegator.0);
319		// DelegatorState:
320		// Twox64Concat(8) + AccountId(20) + Delegator(56 + MaxDelegationsPerDelegator)
321		handle.record_db_read::<Runtime>(
322			84 + (<Runtime as pallet_parachain_staking::Config>::MaxDelegationsPerDelegator::get()
323				as usize),
324		)?;
325		let is_delegator = pallet_parachain_staking::Pallet::<Runtime>::is_delegator(&delegator);
326
327		Ok(is_delegator)
328	}
329
330	#[precompile::public("isCandidate(address)")]
331	#[precompile::public("is_candidate(address)")]
332	#[precompile::view]
333	fn is_candidate(handle: &mut impl PrecompileHandle, candidate: Address) -> EvmResult<bool> {
334		let candidate = Runtime::AddressMapping::into_account_id(candidate.0);
335
336		// CandidateInfo: Twox64Concat(8) + AccountId(20) + CandidateMetadata(105)
337		handle.record_db_read::<Runtime>(133)?;
338		let is_candidate = pallet_parachain_staking::Pallet::<Runtime>::is_candidate(&candidate);
339
340		Ok(is_candidate)
341	}
342
343	#[precompile::public("isSelectedCandidate(address)")]
344	#[precompile::public("is_selected_candidate(address)")]
345	#[precompile::view]
346	fn is_selected_candidate(
347		handle: &mut impl PrecompileHandle,
348		candidate: Address,
349	) -> EvmResult<bool> {
350		let candidate = Runtime::AddressMapping::into_account_id(candidate.0);
351
352		// TotalSelected
353		handle.record_db_read::<Runtime>(4)?;
354		let total_selected = pallet_parachain_staking::Pallet::<Runtime>::total_selected();
355		// SelectedCandidates: total_selected * AccountId(20)
356		handle.record_db_read::<Runtime>(20 * (total_selected as usize))?;
357		let is_selected =
358			pallet_parachain_staking::Pallet::<Runtime>::is_selected_candidate(&candidate);
359
360		Ok(is_selected)
361	}
362
363	#[precompile::public("delegationRequestIsPending(address,address)")]
364	#[precompile::public("delegation_request_is_pending(address,address)")]
365	#[precompile::view]
366	fn delegation_request_is_pending(
367		handle: &mut impl PrecompileHandle,
368		delegator: Address,
369		candidate: Address,
370	) -> EvmResult<bool> {
371		let delegator = Runtime::AddressMapping::into_account_id(delegator.0);
372		let candidate = Runtime::AddressMapping::into_account_id(candidate.0);
373
374		// DelegationScheduledRequests:
375		// Blake2_128Concat(16) + AccountId(20)         <-- collator key
376		// + Blake2_128Concat(16) + AccountId(20)       <-- delegator key
377		// + BoundedVec(
378		//     ScheduledRequest(when_executable, action(Balance))
379		//     * MaxScheduledRequestsPerDelegator
380		//   )
381		//
382		// We keep a conservative upper bound for the encoded size of each
383		// `ScheduledRequest` (42 bytes), and multiply it by the maximum
384		// number of scheduled requests per (collator, delegator) queue.
385		handle.record_db_read::<Runtime>(
386			72 + ScheduledRequest::<BalanceOf<Runtime>>::max_encoded_len()
387				* (<Runtime as pallet_parachain_staking::Config>::MaxScheduledRequestsPerDelegator::get()
388					as usize),
389		)?;
390
391		// If we are not able to get delegator state, we return false
392		// Users can call `is_delegator` to determine when this happens
393		let pending = <pallet_parachain_staking::Pallet<Runtime>>::delegation_request_exists(
394			&candidate, &delegator,
395		);
396
397		Ok(pending)
398	}
399
400	#[precompile::public("candidateExitIsPending(address)")]
401	#[precompile::public("candidate_exit_is_pending(address)")]
402	#[precompile::view]
403	fn candidate_exit_is_pending(
404		handle: &mut impl PrecompileHandle,
405		candidate: Address,
406	) -> EvmResult<bool> {
407		let candidate = Runtime::AddressMapping::into_account_id(candidate.0);
408
409		// CandidateInfo: Twox64Concat(8) + AccountId(20) + CandidateMetadata(105)
410		handle.record_db_read::<Runtime>(133)?;
411
412		// If we are not able to get delegator state, we return false
413		// Users can call `is_candidate` to determine when this happens
414		let pending = if let Some(state) =
415			<pallet_parachain_staking::Pallet<Runtime>>::candidate_info(&candidate)
416		{
417			state.is_leaving()
418		} else {
419			log::trace!(
420				target: "staking-precompile",
421				"Candidate state for {:?} not found, so pending exit is false",
422				candidate
423			);
424			false
425		};
426
427		Ok(pending)
428	}
429
430	#[precompile::public("candidateRequestIsPending(address)")]
431	#[precompile::public("candidate_request_is_pending(address)")]
432	#[precompile::view]
433	fn candidate_request_is_pending(
434		handle: &mut impl PrecompileHandle,
435		candidate: Address,
436	) -> EvmResult<bool> {
437		let candidate = Runtime::AddressMapping::into_account_id(candidate.0);
438
439		// CandidateInfo: Twox64Concat(8) + AccountId(20) + CandidateMetadata(105)
440		handle.record_db_read::<Runtime>(133)?;
441
442		// If we are not able to get candidate metadata, we return false
443		// Users can call `is_candidate` to determine when this happens
444		let pending = if let Some(state) =
445			<pallet_parachain_staking::Pallet<Runtime>>::candidate_info(&candidate)
446		{
447			state.request.is_some()
448		} else {
449			log::trace!(
450				target: "staking-precompile",
451				"Candidate metadata for {:?} not found, so pending request is false",
452				candidate
453			);
454			false
455		};
456
457		Ok(pending)
458	}
459
460	#[precompile::public("delegationAutoCompound(address,address)")]
461	#[precompile::view]
462	fn delegation_auto_compound(
463		handle: &mut impl PrecompileHandle,
464		delegator: Address,
465		candidate: Address,
466	) -> EvmResult<u8> {
467		let delegator = Runtime::AddressMapping::into_account_id(delegator.0);
468		let candidate = Runtime::AddressMapping::into_account_id(candidate.0);
469
470		// AutoCompoundingDelegations:
471		// Blake2128(16) + AccountId(20)
472		// + BoundedVec(
473		// 	AutoCompoundConfig * (MaxTopDelegationsPerCandidate + MaxBottomDelegationsPerCandidate)
474		// )
475		handle.record_db_read::<Runtime>(
476			36 + (
477				22 * (<Runtime as pallet_parachain_staking::Config>::MaxTopDelegationsPerCandidate::get()
478				+ <Runtime as pallet_parachain_staking::Config>::MaxBottomDelegationsPerCandidate::get())
479				as usize),
480		)?;
481
482		let value = <pallet_parachain_staking::Pallet<Runtime>>::delegation_auto_compound(
483			&candidate, &delegator,
484		);
485
486		Ok(value.deconstruct())
487	}
488
489	// Runtime Methods (dispatchables)
490
491	#[precompile::public("joinCandidates(uint256,uint256)")]
492	#[precompile::public("join_candidates(uint256,uint256)")]
493	fn join_candidates(
494		handle: &mut impl PrecompileHandle,
495		amount: U256,
496		candidate_count: Convert<U256, u32>,
497	) -> EvmResult {
498		let amount = Self::u256_to_amount(amount).in_field("amount")?;
499		let candidate_count = candidate_count.converted();
500
501		// Build call with origin.
502		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
503		let call = pallet_parachain_staking::Call::<Runtime>::join_candidates {
504			bond: amount,
505			candidate_count,
506		};
507
508		// Dispatch call (if enough gas).
509		RuntimeHelper::<Runtime>::try_dispatch(
510			handle,
511			frame_system::RawOrigin::Signed(origin).into(),
512			call,
513			0,
514		)?;
515
516		Ok(())
517	}
518
519	#[precompile::public("scheduleLeaveCandidates(uint256)")]
520	#[precompile::public("schedule_leave_candidates(uint256)")]
521	fn schedule_leave_candidates(
522		handle: &mut impl PrecompileHandle,
523		candidate_count: Convert<U256, u32>,
524	) -> EvmResult {
525		let candidate_count = candidate_count.converted();
526
527		// Build call with origin.
528		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
529		let call = pallet_parachain_staking::Call::<Runtime>::schedule_leave_candidates {
530			candidate_count,
531		};
532
533		// Dispatch call (if enough gas).
534		RuntimeHelper::<Runtime>::try_dispatch(
535			handle,
536			frame_system::RawOrigin::Signed(origin).into(),
537			call,
538			0,
539		)?;
540
541		Ok(())
542	}
543
544	#[precompile::public("executeLeaveCandidates(address,uint256)")]
545	#[precompile::public("execute_leave_candidates(address,uint256)")]
546	fn execute_leave_candidates(
547		handle: &mut impl PrecompileHandle,
548		candidate: Address,
549		candidate_count: Convert<U256, u32>,
550	) -> EvmResult {
551		let candidate_count = candidate_count.converted();
552		let candidate = Runtime::AddressMapping::into_account_id(candidate.0);
553
554		// Build call with origin.
555		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
556		let call = pallet_parachain_staking::Call::<Runtime>::execute_leave_candidates {
557			candidate,
558			candidate_delegation_count: candidate_count,
559		};
560
561		// Dispatch call (if enough gas).
562		RuntimeHelper::<Runtime>::try_dispatch(
563			handle,
564			frame_system::RawOrigin::Signed(origin).into(),
565			call,
566			0,
567		)?;
568
569		Ok(())
570	}
571
572	#[precompile::public("cancelLeaveCandidates(uint256)")]
573	#[precompile::public("cancel_leave_candidates(uint256)")]
574	fn cancel_leave_candidates(
575		handle: &mut impl PrecompileHandle,
576		candidate_count: Convert<U256, u32>,
577	) -> EvmResult {
578		let candidate_count = candidate_count.converted();
579
580		// Build call with origin.
581		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
582		let call =
583			pallet_parachain_staking::Call::<Runtime>::cancel_leave_candidates { candidate_count };
584
585		// Dispatch call (if enough gas).
586		RuntimeHelper::<Runtime>::try_dispatch(
587			handle,
588			frame_system::RawOrigin::Signed(origin).into(),
589			call,
590			0,
591		)?;
592
593		Ok(())
594	}
595
596	#[precompile::public("goOffline()")]
597	#[precompile::public("go_offline()")]
598	fn go_offline(handle: &mut impl PrecompileHandle) -> EvmResult {
599		// Build call with origin.
600		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
601		let call = pallet_parachain_staking::Call::<Runtime>::go_offline {};
602
603		// Dispatch call (if enough gas).
604		RuntimeHelper::<Runtime>::try_dispatch(
605			handle,
606			frame_system::RawOrigin::Signed(origin).into(),
607			call,
608			0,
609		)?;
610
611		Ok(())
612	}
613
614	#[precompile::public("goOnline()")]
615	#[precompile::public("go_online()")]
616	fn go_online(handle: &mut impl PrecompileHandle) -> EvmResult {
617		// Build call with origin.
618		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
619		let call = pallet_parachain_staking::Call::<Runtime>::go_online {};
620
621		// Dispatch call (if enough gas).
622		RuntimeHelper::<Runtime>::try_dispatch(
623			handle,
624			frame_system::RawOrigin::Signed(origin).into(),
625			call,
626			0,
627		)?;
628
629		Ok(())
630	}
631
632	#[precompile::public("candidateBondMore(uint256)")]
633	#[precompile::public("candidate_bond_more(uint256)")]
634	fn candidate_bond_more(handle: &mut impl PrecompileHandle, more: U256) -> EvmResult {
635		let more = Self::u256_to_amount(more).in_field("more")?;
636
637		// Build call with origin.
638		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
639		let call = pallet_parachain_staking::Call::<Runtime>::candidate_bond_more { more };
640
641		// Dispatch call (if enough gas).
642		RuntimeHelper::<Runtime>::try_dispatch(
643			handle,
644			frame_system::RawOrigin::Signed(origin).into(),
645			call,
646			0,
647		)?;
648
649		Ok(())
650	}
651
652	#[precompile::public("scheduleCandidateBondLess(uint256)")]
653	#[precompile::public("schedule_candidate_bond_less(uint256)")]
654	fn schedule_candidate_bond_less(handle: &mut impl PrecompileHandle, less: U256) -> EvmResult {
655		let less = Self::u256_to_amount(less).in_field("less")?;
656
657		// Build call with origin.
658		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
659		let call = pallet_parachain_staking::Call::<Runtime>::schedule_candidate_bond_less { less };
660
661		// Dispatch call (if enough gas).
662		RuntimeHelper::<Runtime>::try_dispatch(
663			handle,
664			frame_system::RawOrigin::Signed(origin).into(),
665			call,
666			0,
667		)?;
668
669		Ok(())
670	}
671
672	#[precompile::public("executeCandidateBondLess(address)")]
673	#[precompile::public("execute_candidate_bond_less(address)")]
674	fn execute_candidate_bond_less(
675		handle: &mut impl PrecompileHandle,
676		candidate: Address,
677	) -> EvmResult {
678		let candidate = Runtime::AddressMapping::into_account_id(candidate.0);
679
680		// Build call with origin.
681		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
682		let call =
683			pallet_parachain_staking::Call::<Runtime>::execute_candidate_bond_less { candidate };
684
685		// Dispatch call (if enough gas).
686		RuntimeHelper::<Runtime>::try_dispatch(
687			handle,
688			frame_system::RawOrigin::Signed(origin).into(),
689			call,
690			0,
691		)?;
692
693		Ok(())
694	}
695
696	#[precompile::public("cancelCandidateBondLess()")]
697	#[precompile::public("cancel_candidate_bond_less()")]
698	fn cancel_candidate_bond_less(handle: &mut impl PrecompileHandle) -> EvmResult {
699		// Build call with origin.
700		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
701		let call = pallet_parachain_staking::Call::<Runtime>::cancel_candidate_bond_less {};
702
703		// Dispatch call (if enough gas).
704		RuntimeHelper::<Runtime>::try_dispatch(
705			handle,
706			frame_system::RawOrigin::Signed(origin).into(),
707			call,
708			0,
709		)?;
710
711		Ok(())
712	}
713
714	#[precompile::public("delegateWithAutoCompound(address,uint256,uint8,uint256,uint256,uint256)")]
715	fn delegate_with_auto_compound(
716		handle: &mut impl PrecompileHandle,
717		candidate: Address,
718		amount: U256,
719		auto_compound: u8,
720		candidate_delegation_count: Convert<U256, u32>,
721		candidate_auto_compounding_delegation_count: Convert<U256, u32>,
722		delegator_delegation_count: Convert<U256, u32>,
723	) -> EvmResult {
724		if auto_compound > 100 {
725			return Err(
726				RevertReason::custom("Must be an integer between 0 and 100 included")
727					.in_field("auto_compound")
728					.into(),
729			);
730		}
731
732		let amount = Self::u256_to_amount(amount).in_field("amount")?;
733		let auto_compound = Percent::from_percent(auto_compound);
734		let candidate_delegation_count = candidate_delegation_count.converted();
735		let candidate_auto_compounding_delegation_count =
736			candidate_auto_compounding_delegation_count.converted();
737		let delegator_delegation_count = delegator_delegation_count.converted();
738
739		let candidate = Runtime::AddressMapping::into_account_id(candidate.0);
740
741		// Build call with origin.
742		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
743		let call = pallet_parachain_staking::Call::<Runtime>::delegate_with_auto_compound {
744			candidate,
745			amount,
746			auto_compound,
747			candidate_delegation_count,
748			candidate_auto_compounding_delegation_count,
749			delegation_count: delegator_delegation_count,
750		};
751
752		// Dispatch call (if enough gas).
753		RuntimeHelper::<Runtime>::try_dispatch(
754			handle,
755			frame_system::RawOrigin::Signed(origin).into(),
756			call,
757			0,
758		)?;
759
760		Ok(())
761	}
762
763	#[precompile::public("scheduleRevokeDelegation(address)")]
764	#[precompile::public("schedule_revoke_delegation(address)")]
765	fn schedule_revoke_delegation(
766		handle: &mut impl PrecompileHandle,
767		candidate: Address,
768	) -> EvmResult {
769		let candidate = Runtime::AddressMapping::into_account_id(candidate.0);
770
771		// Build call with origin.
772		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
773		let call = pallet_parachain_staking::Call::<Runtime>::schedule_revoke_delegation {
774			collator: candidate,
775		};
776
777		// Dispatch call (if enough gas).
778		RuntimeHelper::<Runtime>::try_dispatch(
779			handle,
780			frame_system::RawOrigin::Signed(origin).into(),
781			call,
782			0,
783		)?;
784
785		Ok(())
786	}
787
788	#[precompile::public("delegatorBondMore(address,uint256)")]
789	#[precompile::public("delegator_bond_more(address,uint256)")]
790	fn delegator_bond_more(
791		handle: &mut impl PrecompileHandle,
792		candidate: Address,
793		more: U256,
794	) -> EvmResult {
795		let candidate = Runtime::AddressMapping::into_account_id(candidate.0);
796		let more = Self::u256_to_amount(more).in_field("more")?;
797
798		// Build call with origin.
799		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
800		let call =
801			pallet_parachain_staking::Call::<Runtime>::delegator_bond_more { candidate, more };
802
803		// Dispatch call (if enough gas).
804		RuntimeHelper::<Runtime>::try_dispatch(
805			handle,
806			frame_system::RawOrigin::Signed(origin).into(),
807			call,
808			0,
809		)?;
810
811		Ok(())
812	}
813
814	#[precompile::public("scheduleDelegatorBondLess(address,uint256)")]
815	#[precompile::public("schedule_delegator_bond_less(address,uint256)")]
816	fn schedule_delegator_bond_less(
817		handle: &mut impl PrecompileHandle,
818		candidate: Address,
819		less: U256,
820	) -> EvmResult {
821		let candidate = Runtime::AddressMapping::into_account_id(candidate.0);
822		let less = Self::u256_to_amount(less).in_field("less")?;
823
824		// Build call with origin.
825		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
826		let call = pallet_parachain_staking::Call::<Runtime>::schedule_delegator_bond_less {
827			candidate,
828			less,
829		};
830
831		// Dispatch call (if enough gas).
832		RuntimeHelper::<Runtime>::try_dispatch(
833			handle,
834			frame_system::RawOrigin::Signed(origin).into(),
835			call,
836			0,
837		)?;
838
839		Ok(())
840	}
841
842	#[precompile::public("executeDelegationRequest(address,address)")]
843	#[precompile::public("execute_delegation_request(address,address)")]
844	fn execute_delegation_request(
845		handle: &mut impl PrecompileHandle,
846		delegator: Address,
847		candidate: Address,
848	) -> EvmResult {
849		let delegator = Runtime::AddressMapping::into_account_id(delegator.0);
850		let candidate = Runtime::AddressMapping::into_account_id(candidate.0);
851
852		// Build call with origin.
853		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
854		let call = pallet_parachain_staking::Call::<Runtime>::execute_delegation_request {
855			delegator,
856			candidate,
857		};
858
859		// Dispatch call (if enough gas).
860		RuntimeHelper::<Runtime>::try_dispatch(
861			handle,
862			frame_system::RawOrigin::Signed(origin).into(),
863			call,
864			0,
865		)?;
866
867		Ok(())
868	}
869
870	#[precompile::public("cancelDelegationRequest(address)")]
871	#[precompile::public("cancel_delegation_request(address)")]
872	fn cancel_delegation_request(
873		handle: &mut impl PrecompileHandle,
874		candidate: Address,
875	) -> EvmResult {
876		let candidate = Runtime::AddressMapping::into_account_id(candidate.0);
877
878		// Build call with origin.
879		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
880		let call =
881			pallet_parachain_staking::Call::<Runtime>::cancel_delegation_request { candidate };
882
883		// Dispatch call (if enough gas).
884		RuntimeHelper::<Runtime>::try_dispatch(
885			handle,
886			frame_system::RawOrigin::Signed(origin).into(),
887			call,
888			0,
889		)?;
890
891		Ok(())
892	}
893
894	#[precompile::public("setAutoCompound(address,uint8,uint256,uint256)")]
895	fn set_auto_compound(
896		handle: &mut impl PrecompileHandle,
897		candidate: Address,
898		value: u8,
899		candidate_auto_compounding_delegation_count: Convert<U256, u32>,
900		delegator_delegation_count: Convert<U256, u32>,
901	) -> EvmResult {
902		if value > 100 {
903			return Err(
904				RevertReason::custom("Must be an integer between 0 and 100 included")
905					.in_field("value")
906					.into(),
907			);
908		}
909
910		let value = Percent::from_percent(value);
911		let candidate_auto_compounding_delegation_count_hint =
912			candidate_auto_compounding_delegation_count.converted();
913		let delegation_count_hint = delegator_delegation_count.converted();
914
915		let candidate = Runtime::AddressMapping::into_account_id(candidate.0);
916
917		// Build call with origin.
918		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
919		let call = pallet_parachain_staking::Call::<Runtime>::set_auto_compound {
920			candidate,
921			value,
922			candidate_auto_compounding_delegation_count_hint,
923			delegation_count_hint,
924		};
925
926		// Dispatch call (if enough gas).
927		RuntimeHelper::<Runtime>::try_dispatch(
928			handle,
929			frame_system::RawOrigin::Signed(origin).into(),
930			call,
931			0,
932		)?;
933
934		Ok(())
935	}
936
937	#[precompile::public("getDelegatorTotalStaked(address)")]
938	#[precompile::view]
939	fn get_delegator_total_staked(
940		handle: &mut impl PrecompileHandle,
941		delegator: Address,
942	) -> EvmResult<U256> {
943		// DelegatorState:
944		// Twox64Concat(8) + AccountId(20) + Delegator(56 + MaxDelegationsPerDelegator)
945		handle.record_db_read::<Runtime>(
946			84 + (<Runtime as pallet_parachain_staking::Config>::MaxDelegationsPerDelegator::get()
947				as usize),
948		)?;
949
950		let delegator = Runtime::AddressMapping::into_account_id(delegator.0);
951
952		let amount = <pallet_parachain_staking::Pallet<Runtime>>::delegator_state(&delegator)
953			.map(|state| state.total)
954			.unwrap_or_default();
955
956		Ok(amount.into())
957	}
958
959	#[precompile::public("getCandidateTotalCounted(address)")]
960	#[precompile::view]
961	fn get_candidate_total_counted(
962		handle: &mut impl PrecompileHandle,
963		candidate: Address,
964	) -> EvmResult<U256> {
965		// CandidateInfo: Twox64Concat(8) + AccountId(20) + CandidateMetadata(105)
966		handle.record_db_read::<Runtime>(133)?;
967
968		let candidate = Runtime::AddressMapping::into_account_id(candidate.0);
969
970		let amount = <pallet_parachain_staking::Pallet<Runtime>>::candidate_info(&candidate)
971			.map(|state| state.total_counted)
972			.unwrap_or_default();
973
974		Ok(amount.into())
975	}
976
977	fn u256_to_amount(value: U256) -> MayRevert<BalanceOf<Runtime>> {
978		value
979			.try_into()
980			.map_err(|_| RevertReason::value_is_too_large("balance type").into())
981	}
982}