pallet_vesting_precompiles/
lib.rs1#![cfg_attr(not(feature = "std"), no_std)]
19
20extern crate alloc;
21
22use alloc::vec::Vec;
23use alloy_core::sol_types::SolValue;
24use core::{marker::PhantomData, num::NonZero};
25use frame_support::traits::{Get, LockableCurrency, VestingSchedule};
26use frame_system::pallet_prelude::BlockNumberFor;
27use pallet_revive::{
28 Config,
29 precompiles::{AddressMatcher, Error, Ext, H160, Precompile, RuntimeCosts, U256},
30};
31use pallet_vesting::{VestingInfo, WeightInfo as _};
32use sp_runtime::traits::StaticLookup;
33
34alloy_core::sol!("IVesting.sol");
35
36pub use pallet::Pallet;
37pub mod weights;
38pub use weights::WeightInfo;
39
40#[cfg(feature = "runtime-benchmarks")]
41pub mod benchmarking;
42
43#[cfg(all(test, feature = "runtime-benchmarks"))]
44pub mod mock;
45
46#[cfg(all(test, feature = "runtime-benchmarks"))]
47mod tests;
48
49fn ensure_mutable<T: Config>(env: &impl Ext<T = T>) -> Result<(), Error> {
50 if env.is_read_only() {
51 return Err(pallet_revive::Error::<T>::StateChangeDenied.into());
52 }
53 if env.is_delegate_call() {
54 return Err(pallet_revive::Error::<T>::PrecompileDelegateDenied.into());
55 }
56 Ok(())
57}
58
59fn caller_account_id<T: Config>(
60 env: &impl Ext<T = T>,
61 context: &str,
62) -> Result<T::AccountId, Error> {
63 env.caller()
64 .account_id()
65 .map_err(|e| {
66 Error::Revert(alloc::format!("{context}: caller has no account id: {e:?}").into())
67 })
68 .cloned()
69}
70
71#[frame_support::pallet]
73pub mod pallet {
74 #[pallet::config]
75 pub trait Config:
76 frame_system::Config + pallet_revive::Config + pallet_vesting::Config
77 {
78 type WeightInfo: crate::weights::WeightInfo;
80 }
81
82 #[pallet::pallet]
83 pub struct Pallet<T>(_);
84}
85
86pub struct Vesting<T>(PhantomData<T>);
87
88type VestingBalance<T> =
90 <<T as pallet_vesting::Config>::Currency as frame_support::traits::Currency<
91 <T as frame_system::Config>::AccountId,
92 >>::Balance;
93
94type MaxLocksOf<T> = <<T as pallet_vesting::Config>::Currency as LockableCurrency<
96 <T as frame_system::Config>::AccountId,
97>>::MaxLocks;
98
99impl<T: Config + pallet_vesting::Config + pallet::Config> Precompile for Vesting<T>
100where
101 VestingBalance<T>: Into<U256>,
102 VestingBalance<T>: From<<T as Config>::Balance>,
103 <T as Config>::Balance: From<VestingBalance<T>>,
104{
105 type T = T;
106 type Interface = IVesting::IVestingCalls;
107 const MATCHER: AddressMatcher = AddressMatcher::Fixed(NonZero::new(0x0902).unwrap());
108 const HAS_CONTRACT_INFO: bool = false;
109
110 fn call(
111 _address: &[u8; 20],
112 input: &Self::Interface,
113 env: &mut impl Ext<T = Self::T>,
114 ) -> Result<Vec<u8>, Error> {
115 use IVesting::IVestingCalls;
116 match input {
117 IVestingCalls::vest(IVesting::vestCall {}) => {
118 let max_locks = MaxLocksOf::<T>::get();
123 let dispatch_weight = <T as pallet_vesting::Config>::WeightInfo::vest_locked(
124 max_locks,
125 T::MAX_VESTING_SCHEDULES,
126 )
127 .max(<T as pallet_vesting::Config>::WeightInfo::vest_unlocked(
128 max_locks,
129 T::MAX_VESTING_SCHEDULES,
130 ));
131 env.frame_meter_mut()
132 .charge_weight_token(RuntimeCosts::Precompile(dispatch_weight))?;
133
134 ensure_mutable::<T>(env)?;
135
136 let account_id = caller_account_id(env, "vest")?;
137 let origin = frame_system::RawOrigin::Signed(account_id).into();
138 pallet_vesting::Pallet::<T>::vest(origin)
139 .map_err(|e| Error::Revert(alloc::format!("vest failed: {:?}", e).into()))?;
140 Ok(Vec::new())
141 },
142 IVestingCalls::vestOther(IVesting::vestOtherCall { target }) => {
143 let max_locks = MaxLocksOf::<T>::get();
146 let dispatch_weight = <T as pallet_vesting::Config>::WeightInfo::vest_other_locked(
147 max_locks,
148 T::MAX_VESTING_SCHEDULES,
149 )
150 .max(<T as pallet_vesting::Config>::WeightInfo::vest_other_unlocked(
151 max_locks,
152 T::MAX_VESTING_SCHEDULES,
153 ));
154 env.frame_meter_mut()
155 .charge_weight_token(RuntimeCosts::Precompile(dispatch_weight))?;
156
157 ensure_mutable::<T>(env)?;
158
159 let caller_account = caller_account_id(env, "vestOther")?;
160 let target_account = env.to_account_id(&H160::from_slice(target.as_slice()));
161 let target_lookup = T::Lookup::unlookup(target_account);
162
163 let origin = frame_system::RawOrigin::Signed(caller_account).into();
164 pallet_vesting::Pallet::<T>::vest_other(origin, target_lookup).map_err(|e| {
165 Error::Revert(alloc::format!("vestOther failed: {:?}", e).into())
166 })?;
167 Ok(Vec::new())
168 },
169 IVestingCalls::vestedTransfer(IVesting::vestedTransferCall {
170 target,
171 locked,
172 perBlock,
173 startingBlock,
174 }) => {
175 let max_locks = MaxLocksOf::<T>::get();
178 let dispatch_weight = <T as pallet_vesting::Config>::WeightInfo::vested_transfer(
179 max_locks,
180 T::MAX_VESTING_SCHEDULES,
181 );
182 env.frame_meter_mut()
183 .charge_weight_token(RuntimeCosts::Precompile(dispatch_weight))?;
184
185 ensure_mutable::<T>(env)?;
186
187 let caller_account = caller_account_id(env, "vestedTransfer")?;
188 let target_account = env.to_account_id(&H160::from_slice(target.as_slice()));
189 let target_lookup = T::Lookup::unlookup(target_account);
190
191 let locked: VestingBalance<T> = {
192 let balance: <T as Config>::Balance =
193 U256::from_big_endian(&locked.to_be_bytes::<32>())
194 .try_into()
195 .map_err(|_| Error::Revert("vestedTransfer: locked overflow".into()))?;
196 <VestingBalance<T> as From<<T as Config>::Balance>>::from(balance)
197 };
198 let per_block: VestingBalance<T> = {
199 let balance: <T as Config>::Balance =
200 U256::from_big_endian(&perBlock.to_be_bytes::<32>()).try_into().map_err(
201 |_| Error::Revert("vestedTransfer: perBlock overflow".into()),
202 )?;
203 <VestingBalance<T> as From<<T as Config>::Balance>>::from(balance)
204 };
205 let starting_block: BlockNumberFor<T> =
206 U256::from_big_endian(&startingBlock.to_be_bytes::<32>()).try_into().map_err(
207 |_| Error::Revert("vestedTransfer: startingBlock overflow".into()),
208 )?;
209
210 let schedule = VestingInfo::new(locked, per_block, starting_block);
211 let origin = frame_system::RawOrigin::Signed(caller_account).into();
212 pallet_vesting::Pallet::<T>::vested_transfer(origin, target_lookup, schedule)
213 .map_err(|e| {
214 Error::Revert(alloc::format!("vestedTransfer failed: {:?}", e).into())
215 })?;
216 Ok(Vec::new())
217 },
218 IVestingCalls::vestingBalance(IVesting::vestingBalanceCall {}) => {
223 env.frame_meter_mut().charge_weight_token(RuntimeCosts::Precompile(
224 <<T as pallet::Config>::WeightInfo as weights::WeightInfo>::vesting_balance(),
225 ))?;
226
227 let account_id = caller_account_id(env, "vestingBalance")?;
228
229 let maybe_locked =
230 <pallet_vesting::Pallet<T> as VestingSchedule<T::AccountId>>::vesting_balance(
231 &account_id,
232 );
233
234 let locked = maybe_locked.unwrap_or_default();
235 Ok(U256::from(locked.into()).to_big_endian().abi_encode())
236 },
237 IVestingCalls::vestingBalanceOf(IVesting::vestingBalanceOfCall { target }) => {
238 env.frame_meter_mut().charge_weight_token(RuntimeCosts::Precompile(
239 <<T as pallet::Config>::WeightInfo as weights::WeightInfo>::vesting_balance_of(
240 ),
241 ))?;
242
243 let account_id = env.to_account_id(&H160::from_slice(target.as_slice()));
244
245 let maybe_locked =
246 <pallet_vesting::Pallet<T> as VestingSchedule<T::AccountId>>::vesting_balance(
247 &account_id,
248 );
249
250 let locked = maybe_locked.unwrap_or_default();
251 Ok(U256::from(locked.into()).to_big_endian().abi_encode())
252 },
253 }
254 }
255}