pallet_evm_precompile_crowdloan_rewards/
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 pallet-crowdloan-rewards runtime methods via the EVM
18
19#![cfg_attr(not(feature = "std"), no_std)]
20
21use fp_evm::PrecompileHandle;
22use frame_support::{
23	dispatch::{GetDispatchInfo, PostDispatchInfo},
24	traits::Currency,
25};
26use pallet_evm::AddressMapping;
27use parity_scale_codec::EncodeLike;
28use precompile_utils::prelude::*;
29
30use sp_core::{H160, U256};
31use sp_runtime::traits::Dispatchable;
32use sp_std::{
33	convert::{TryFrom, TryInto},
34	fmt::Debug,
35	marker::PhantomData,
36};
37
38#[cfg(test)]
39mod mock;
40#[cfg(test)]
41mod tests;
42
43pub type BalanceOf<Runtime> =
44	<<Runtime as pallet_crowdloan_rewards::Config>::RewardCurrency as Currency<
45		<Runtime as frame_system::Config>::AccountId,
46	>>::Balance;
47
48/// A precompile to wrap the functionality from pallet_crowdloan_rewards.
49pub struct CrowdloanRewardsPrecompile<Runtime>(PhantomData<Runtime>);
50
51#[precompile_utils::precompile]
52impl<Runtime> CrowdloanRewardsPrecompile<Runtime>
53where
54	Runtime: pallet_crowdloan_rewards::Config + pallet_evm::Config + frame_system::Config,
55	BalanceOf<Runtime>: TryFrom<U256> + TryInto<u128> + Debug,
56	<Runtime as pallet_crowdloan_rewards::Config>::RewardCurrency:
57		frame_support::traits::Currency<<Runtime as frame_system::pallet::Config>::AccountId>,
58	<Runtime as frame_system::pallet::Config>::RuntimeCall:
59		Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
60	<Runtime as frame_system::pallet::Config>::RuntimeCall:
61		From<pallet_crowdloan_rewards::Call<Runtime>>,
62	<Runtime as pallet_evm::Config>::AddressMapping:
63		AddressMapping<<Runtime as frame_system::pallet::Config>::AccountId>,
64	<Runtime as frame_system::Config>::AccountId: From<<Runtime as frame_system::pallet::Config>::AccountId>
65		+ Into<<Runtime as frame_system::pallet::Config>::AccountId>
66		+ EncodeLike<<Runtime as frame_system::pallet::Config>::AccountId>,
67{
68	// The accessors are first.
69	#[precompile::public("isContributor(address)")]
70	#[precompile::public("is_contributor(address)")]
71	#[precompile::view]
72	fn is_contributor(handle: &mut impl PrecompileHandle, contributor: Address) -> EvmResult<bool> {
73		// AccountsPayable: Blake2128(16) + 20 + RewardInfo(16 + 16 + UnBoundedVec<AccountId32(32)>)
74		// TODO RewardInfo.contributed_relay_addresses is unbounded, we set a safe length of 100.
75		handle.record_db_read::<Runtime>(3268)?;
76
77		let contributor: H160 = contributor.into();
78
79		let account = Runtime::AddressMapping::into_account_id(contributor);
80
81		log::trace!(
82			target: "crowdloan-rewards-precompile",
83			"Checking whether {:?} is a contributor",
84			contributor
85		);
86
87		// fetch data from pallet
88		let is_contributor: bool =
89			pallet_crowdloan_rewards::Pallet::<Runtime>::accounts_payable(account.into()).is_some();
90
91		log::trace!(target: "crowldoan-rewards-precompile", "Result from pallet is {:?}", is_contributor);
92
93		Ok(is_contributor)
94	}
95
96	#[precompile::public("rewardInfo(address)")]
97	#[precompile::public("reward_info(address)")]
98	#[precompile::view]
99	fn reward_info(
100		handle: &mut impl PrecompileHandle,
101		contributor: Address,
102	) -> EvmResult<(U256, U256)> {
103		// AccountsPayable: Blake2128(16) + 20 + RewardInfo(16 + 16 + UnBoundedVec<AccountId32(32)>)
104		// TODO RewardInfo.contributed_relay_addresses is unbounded, we set a safe length of 100.
105		handle.record_db_read::<Runtime>(3268)?;
106
107		let contributor: H160 = contributor.into();
108
109		let account = Runtime::AddressMapping::into_account_id(contributor);
110
111		log::trace!(
112			target: "crowdloan-rewards-precompile",
113			"Checking reward info for {:?}",
114			contributor
115		);
116
117		// fetch data from pallet
118		let reward_info =
119			pallet_crowdloan_rewards::Pallet::<Runtime>::accounts_payable(account.into());
120
121		let (total, claimed): (U256, U256) = if let Some(reward_info) = reward_info {
122			let total_reward: u128 = reward_info
123				.total_reward
124				.try_into()
125				.map_err(|_| RevertReason::value_is_too_large("balance type"))?;
126			let claimed_reward: u128 = reward_info
127				.claimed_reward
128				.try_into()
129				.map_err(|_| RevertReason::value_is_too_large("balance type"))?;
130
131			(total_reward.into(), claimed_reward.into())
132		} else {
133			(0u128.into(), 0u128.into())
134		};
135
136		log::trace!(
137			target: "crowldoan-rewards-precompile", "Result from pallet is {:?}  {:?}",
138			total, claimed
139		);
140
141		Ok((total, claimed))
142	}
143
144	#[precompile::public("claim()")]
145	fn claim(handle: &mut impl PrecompileHandle) -> EvmResult {
146		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
147		let call = pallet_crowdloan_rewards::Call::<Runtime>::claim {};
148
149		RuntimeHelper::<Runtime>::try_dispatch(
150			handle,
151			frame_system::RawOrigin::Signed(origin.into()).into(),
152			call,
153			0,
154		)?;
155
156		Ok(())
157	}
158
159	#[precompile::public("updateRewardAddress(address)")]
160	#[precompile::public("update_reward_address(address)")]
161	fn update_reward_address(
162		handle: &mut impl PrecompileHandle,
163		new_address: Address,
164	) -> EvmResult {
165		log::trace!(
166			target: "crowdloan-rewards-precompile",
167			"In update_reward_address dispatchable wrapper"
168		);
169
170		let new_address: H160 = new_address.into();
171
172		let new_reward_account = Runtime::AddressMapping::into_account_id(new_address);
173
174		log::trace!(target: "crowdloan-rewards-precompile", "New account is {:?}", new_address);
175
176		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
177		let call = pallet_crowdloan_rewards::Call::<Runtime>::update_reward_address {
178			new_reward_account: new_reward_account.into(),
179		};
180
181		RuntimeHelper::<Runtime>::try_dispatch(
182			handle,
183			frame_system::RawOrigin::Signed(origin.into()).into(),
184			call,
185			0,
186		)?;
187
188		Ok(())
189	}
190}