pallet_evm_precompile_relay_encoder/
lib.rs1#![cfg_attr(not(feature = "std"), no_std)]
20
21use cumulus_primitives_core::relay_chain;
22
23use fp_evm::PrecompileHandle;
24use frame_support::{
25 dispatch::{GetDispatchInfo, PostDispatchInfo},
26 ensure,
27 traits::ConstU32,
28};
29use pallet_staking::RewardDestination;
30use precompile_utils::prelude::*;
31use sp_core::{H256, U256};
32use sp_runtime::{traits::Dispatchable, AccountId32, Perbill};
33use sp_std::vec::Vec;
34use sp_std::{convert::TryInto, marker::PhantomData};
35use xcm_primitives::{AvailableStakeCalls, HrmpAvailableCalls, HrmpEncodeCall, StakeEncodeCall};
36
37#[cfg(test)]
38mod mock;
39#[cfg(test)]
40mod test_relay_runtime;
41#[cfg(test)]
42mod tests;
43
44pub const REWARD_DESTINATION_SIZE_LIMIT: u32 = 2u32.pow(16);
45pub const ARRAY_LIMIT: u32 = 512;
46type GetArrayLimit = ConstU32<ARRAY_LIMIT>;
47type GetRewardDestinationSizeLimit = ConstU32<REWARD_DESTINATION_SIZE_LIMIT>;
48
49pub struct RelayEncoderPrecompile<Runtime, StakingTransactor>(
51 PhantomData<(Runtime, StakingTransactor)>,
52);
53
54#[precompile_utils::precompile]
55impl<Runtime, StakingTransactor> RelayEncoderPrecompile<Runtime, StakingTransactor>
56where
57 Runtime: pallet_evm::Config + pallet_xcm_transactor::Config,
58 Runtime::RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
59 StakingTransactor: sp_core::Get<<Runtime as pallet_xcm_transactor::Config>::Transactor>,
60{
61 #[precompile::public("encodeBond(uint256,bytes)")]
62 #[precompile::public("encode_bond(uint256,bytes)")]
63 #[precompile::view]
64 fn encode_bond(
65 handle: &mut impl PrecompileHandle,
66 amount: U256,
67 reward_destination: RewardDestinationWrapper,
68 ) -> EvmResult<UnboundedBytes> {
69 handle.record_cost(1000)?;
72
73 let relay_amount = u256_to_relay_amount(amount)?;
74 let reward_destination = reward_destination.into();
75
76 let encoded = pallet_xcm_transactor::Pallet::<Runtime>::encode_call(
77 StakingTransactor::get(),
78 AvailableStakeCalls::Bond(relay_amount, reward_destination),
79 )
80 .as_slice()
81 .into();
82
83 Ok(encoded)
84 }
85
86 #[precompile::public("encodeBondExtra(uint256)")]
87 #[precompile::public("encode_bond_extra(uint256)")]
88 #[precompile::view]
89 fn encode_bond_extra(
90 handle: &mut impl PrecompileHandle,
91 amount: U256,
92 ) -> EvmResult<UnboundedBytes> {
93 handle.record_cost(1000)?;
96
97 let relay_amount = u256_to_relay_amount(amount)?;
98 let encoded = pallet_xcm_transactor::Pallet::<Runtime>::encode_call(
99 StakingTransactor::get(),
100 AvailableStakeCalls::BondExtra(relay_amount),
101 )
102 .as_slice()
103 .into();
104
105 Ok(encoded)
106 }
107
108 #[precompile::public("encodeUnbond(uint256)")]
109 #[precompile::public("encode_unbond(uint256)")]
110 #[precompile::view]
111 fn encode_unbond(
112 handle: &mut impl PrecompileHandle,
113 amount: U256,
114 ) -> EvmResult<UnboundedBytes> {
115 handle.record_cost(1000)?;
118
119 let relay_amount = u256_to_relay_amount(amount)?;
120
121 let encoded = pallet_xcm_transactor::Pallet::<Runtime>::encode_call(
122 StakingTransactor::get(),
123 AvailableStakeCalls::Unbond(relay_amount),
124 )
125 .as_slice()
126 .into();
127
128 Ok(encoded)
129 }
130
131 #[precompile::public("encodeWithdrawUnbonded(uint32)")]
132 #[precompile::public("encode_withdraw_unbonded(uint32)")]
133 #[precompile::view]
134 fn encode_withdraw_unbonded(
135 handle: &mut impl PrecompileHandle,
136 slashes: u32,
137 ) -> EvmResult<UnboundedBytes> {
138 handle.record_cost(1000)?;
141
142 let encoded = pallet_xcm_transactor::Pallet::<Runtime>::encode_call(
143 StakingTransactor::get(),
144 AvailableStakeCalls::WithdrawUnbonded(slashes),
145 )
146 .as_slice()
147 .into();
148
149 Ok(encoded)
150 }
151
152 #[precompile::public("encodeValidate(uint256,bool)")]
153 #[precompile::public("encode_validate(uint256,bool)")]
154 #[precompile::view]
155 fn encode_validate(
156 handle: &mut impl PrecompileHandle,
157 commission: Convert<U256, u32>,
158 blocked: bool,
159 ) -> EvmResult<UnboundedBytes> {
160 handle.record_cost(1000)?;
163
164 let fraction = Perbill::from_parts(commission.converted());
165 let encoded = pallet_xcm_transactor::Pallet::<Runtime>::encode_call(
166 StakingTransactor::get(),
167 AvailableStakeCalls::Validate(pallet_staking::ValidatorPrefs {
168 commission: fraction,
169 blocked: blocked,
170 }),
171 )
172 .as_slice()
173 .into();
174
175 Ok(encoded)
176 }
177
178 #[precompile::public("encodeNominate(bytes32[])")]
179 #[precompile::public("encode_nominate(bytes32[])")]
180 #[precompile::view]
181 fn encode_nominate(
182 handle: &mut impl PrecompileHandle,
183 nominees: BoundedVec<H256, GetArrayLimit>,
184 ) -> EvmResult<UnboundedBytes> {
185 handle.record_cost(1000)?;
188
189 let nominees: Vec<_> = nominees.into();
190 let nominated: Vec<AccountId32> = nominees
191 .iter()
192 .map(|&add| {
193 let as_bytes: [u8; 32] = add.into();
194 as_bytes.into()
195 })
196 .collect();
197 let encoded = pallet_xcm_transactor::Pallet::<Runtime>::encode_call(
198 StakingTransactor::get(),
199 AvailableStakeCalls::Nominate(nominated),
200 )
201 .as_slice()
202 .into();
203
204 Ok(encoded)
205 }
206
207 #[precompile::public("encodeChill()")]
208 #[precompile::public("encode_chill()")]
209 #[precompile::view]
210 fn encode_chill(handle: &mut impl PrecompileHandle) -> EvmResult<UnboundedBytes> {
211 handle.record_cost(1000)?;
214
215 let encoded = pallet_xcm_transactor::Pallet::<Runtime>::encode_call(
216 StakingTransactor::get(),
217 AvailableStakeCalls::Chill,
218 )
219 .as_slice()
220 .into();
221
222 Ok(encoded)
223 }
224
225 #[precompile::public("encodeSetPayee(bytes)")]
226 #[precompile::public("encode_set_payee(bytes)")]
227 #[precompile::view]
228 fn encode_set_payee(
229 handle: &mut impl PrecompileHandle,
230 reward_destination: RewardDestinationWrapper,
231 ) -> EvmResult<UnboundedBytes> {
232 handle.record_cost(1000)?;
235
236 let reward_destination = reward_destination.into();
237
238 let encoded = pallet_xcm_transactor::Pallet::<Runtime>::encode_call(
239 StakingTransactor::get(),
240 AvailableStakeCalls::SetPayee(reward_destination),
241 )
242 .as_slice()
243 .into();
244
245 Ok(encoded)
246 }
247
248 #[precompile::public("encodeSetController()")]
249 #[precompile::public("encode_set_controller()")]
250 #[precompile::view]
251 fn encode_set_controller(handle: &mut impl PrecompileHandle) -> EvmResult<UnboundedBytes> {
252 handle.record_cost(1000)?;
255
256 let encoded = pallet_xcm_transactor::Pallet::<Runtime>::encode_call(
257 StakingTransactor::get(),
258 AvailableStakeCalls::SetController,
259 )
260 .as_slice()
261 .into();
262
263 Ok(encoded)
264 }
265
266 #[precompile::public("encodeRebond(uint256)")]
267 #[precompile::public("encode_rebond(uint256)")]
268 #[precompile::view]
269 fn encode_rebond(
270 handle: &mut impl PrecompileHandle,
271 amount: U256,
272 ) -> EvmResult<UnboundedBytes> {
273 handle.record_cost(1000)?;
276
277 let relay_amount = u256_to_relay_amount(amount)?;
278 let encoded = pallet_xcm_transactor::Pallet::<Runtime>::encode_call(
279 StakingTransactor::get(),
280 AvailableStakeCalls::Rebond(relay_amount),
281 )
282 .as_slice()
283 .into();
284
285 Ok(encoded)
286 }
287 #[precompile::public("encodeHrmpInitOpenChannel(uint32,uint32,uint32)")]
288 #[precompile::public("encode_hrmp_init_open_channel(uint32,uint32,uint32)")]
289 #[precompile::view]
290 fn encode_hrmp_init_open_channel(
291 handle: &mut impl PrecompileHandle,
292 recipient: u32,
293 max_capacity: u32,
294 max_message_size: u32,
295 ) -> EvmResult<UnboundedBytes> {
296 handle.record_cost(1000)?;
299
300 let encoded = pallet_xcm_transactor::Pallet::<Runtime>::hrmp_encode_call(
301 HrmpAvailableCalls::InitOpenChannel(recipient.into(), max_capacity, max_message_size),
302 )
303 .map_err(|_| {
304 RevertReason::custom("Non-implemented hrmp encoding for transactor")
305 .in_field("transactor")
306 })?
307 .as_slice()
308 .into();
309 Ok(encoded)
310 }
311
312 #[precompile::public("encodeHrmpAcceptOpenChannel(uint32)")]
313 #[precompile::public("encode_hrmp_accept_open_channel(uint32)")]
314 #[precompile::view]
315 fn encode_hrmp_accept_open_channel(
316 handle: &mut impl PrecompileHandle,
317 sender: u32,
318 ) -> EvmResult<UnboundedBytes> {
319 handle.record_cost(1000)?;
322
323 let encoded = pallet_xcm_transactor::Pallet::<Runtime>::hrmp_encode_call(
324 HrmpAvailableCalls::AcceptOpenChannel(sender.into()),
325 )
326 .map_err(|_| {
327 RevertReason::custom("Non-implemented hrmp encoding for transactor")
328 .in_field("transactor")
329 })?
330 .as_slice()
331 .into();
332 Ok(encoded)
333 }
334
335 #[precompile::public("encodeHrmpCloseChannel(uint32,uint32)")]
336 #[precompile::public("encode_hrmp_close_channel(uint32,uint32)")]
337 #[precompile::view]
338 fn encode_hrmp_close_channel(
339 handle: &mut impl PrecompileHandle,
340 sender: u32,
341 recipient: u32,
342 ) -> EvmResult<UnboundedBytes> {
343 handle.record_cost(1000)?;
346
347 let encoded = pallet_xcm_transactor::Pallet::<Runtime>::hrmp_encode_call(
348 HrmpAvailableCalls::CloseChannel(relay_chain::HrmpChannelId {
349 sender: sender.into(),
350 recipient: recipient.into(),
351 }),
352 )
353 .map_err(|_| {
354 RevertReason::custom("Non-implemented hrmp encoding for transactor")
355 .in_field("transactor")
356 })?
357 .as_slice()
358 .into();
359 Ok(encoded)
360 }
361
362 #[precompile::public("encodeHrmpCancelOpenRequest(uint32,uint32,uint32)")]
363 #[precompile::public("encode_hrmp_cancel_open_request(uint32,uint32,uint32)")]
364 #[precompile::view]
365 fn encode_hrmp_cancel_open_request(
366 handle: &mut impl PrecompileHandle,
367 sender: u32,
368 recipient: u32,
369 open_requests: u32,
370 ) -> EvmResult<UnboundedBytes> {
371 handle.record_cost(1000)?;
374
375 let encoded = pallet_xcm_transactor::Pallet::<Runtime>::hrmp_encode_call(
376 HrmpAvailableCalls::CancelOpenRequest(
377 relay_chain::HrmpChannelId {
378 sender: sender.into(),
379 recipient: recipient.into(),
380 },
381 open_requests,
382 ),
383 )
384 .map_err(|_| {
385 RevertReason::custom("Non-implemented hrmp encoding for transactor")
386 .in_field("transactor")
387 })?
388 .as_slice()
389 .into();
390 Ok(encoded)
391 }
392}
393
394pub fn u256_to_relay_amount(value: U256) -> EvmResult<relay_chain::Balance> {
395 value
396 .try_into()
397 .map_err(|_| revert("amount is too large for provided balance type"))
398}
399
400#[derive(Clone, Eq, PartialEq)]
402pub struct RewardDestinationWrapper(RewardDestination<AccountId32>);
403
404impl From<RewardDestination<AccountId32>> for RewardDestinationWrapper {
405 fn from(reward_dest: RewardDestination<AccountId32>) -> Self {
406 RewardDestinationWrapper(reward_dest)
407 }
408}
409
410impl Into<RewardDestination<AccountId32>> for RewardDestinationWrapper {
411 fn into(self) -> RewardDestination<AccountId32> {
412 self.0
413 }
414}
415
416impl solidity::Codec for RewardDestinationWrapper {
417 fn read(reader: &mut solidity::codec::Reader) -> MayRevert<Self> {
418 let reward_destination = reader.read::<BoundedBytes<GetRewardDestinationSizeLimit>>()?;
419 let reward_destination_bytes: Vec<_> = reward_destination.into();
420 ensure!(
421 reward_destination_bytes.len() > 0,
422 RevertReason::custom("Reward destinations cannot be empty")
423 );
424 let mut encoded_reward_destination =
426 solidity::codec::Reader::new(&reward_destination_bytes);
427
428 let enum_selector = encoded_reward_destination.read_raw_bytes(1)?;
430 match enum_selector[0] {
432 0u8 => Ok(RewardDestinationWrapper(RewardDestination::Staked)),
433 1u8 => Ok(RewardDestinationWrapper(RewardDestination::Stash)),
434 #[allow(deprecated)]
436 2u8 => Ok(RewardDestinationWrapper(RewardDestination::Controller)),
437 3u8 => {
438 let address = encoded_reward_destination.read::<H256>()?;
439 Ok(RewardDestinationWrapper(RewardDestination::Account(
440 address.as_fixed_bytes().clone().into(),
441 )))
442 }
443 4u8 => Ok(RewardDestinationWrapper(RewardDestination::None)),
444 _ => Err(RevertReason::custom("Unknown reward destination").into()),
445 }
446 }
447
448 fn write(writer: &mut solidity::codec::Writer, value: Self) {
449 let mut encoded: Vec<u8> = Vec::new();
450 let encoded_bytes: UnboundedBytes = match value.0 {
451 RewardDestination::Staked => {
452 encoded.push(0);
453 encoded.as_slice().into()
454 }
455 RewardDestination::Stash => {
456 encoded.push(1);
457 encoded.as_slice().into()
458 }
459 #[allow(deprecated)]
461 RewardDestination::Controller => {
462 encoded.push(2);
463 encoded.as_slice().into()
464 }
465 RewardDestination::Account(address) => {
466 encoded.push(3);
467 let address_bytes: [u8; 32] = address.into();
468 encoded.append(&mut address_bytes.to_vec());
469 encoded.as_slice().into()
470 }
471 RewardDestination::None => {
472 encoded.push(4);
473 encoded.as_slice().into()
474 }
475 };
476 solidity::Codec::write(writer, encoded_bytes);
477 }
478
479 fn has_static_size() -> bool {
480 false
481 }
482
483 fn signature() -> String {
484 UnboundedBytes::signature()
485 }
486}