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