pallet_evm_precompile_dispatch_lockdrop/
lib.rs

1// This file is part of Astar.
2
3// Copyright (C) Stake Technologies Pte.Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later
5
6// Astar is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// Astar is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with Astar. If not, see <http://www.gnu.org/licenses/>.
18
19#![cfg_attr(not(feature = "std"), no_std)]
20
21extern crate alloc;
22
23use core::marker::PhantomData;
24use fp_evm::PrecompileHandle;
25use frame_support::pallet_prelude::IsType;
26use frame_support::traits::Get;
27use frame_support::weights::Weight;
28use frame_support::{
29    dispatch::{GetDispatchInfo, PostDispatchInfo},
30    traits::ConstU32,
31};
32use frame_system::Config;
33use pallet_evm::GasWeightMapping;
34use pallet_evm_precompile_dispatch::DispatchValidateT;
35use parity_scale_codec::DecodeLimit;
36use precompile_utils::prelude::{revert, BoundedBytes, RuntimeHelper};
37use precompile_utils::EvmResult;
38use sp_core::{crypto::AccountId32, H160, H256};
39use sp_io::hashing::keccak_256;
40use sp_runtime::traits::Dispatchable;
41use sp_std::vec::Vec;
42
43#[cfg(test)]
44mod mock;
45#[cfg(test)]
46mod tests;
47
48pub const LOG_TARGET: &str = "precompile::dispatch-lockdrop";
49
50// ECDSA PublicKey
51type ECDSAPublic = ConstU32<64>;
52
53// `DecodeLimit` specifies the max depth a call can use when decoding, as unbounded depth
54// can be used to overflow the stack.
55// Default value is 8, which is the same as in XCM call decoding.
56pub struct DispatchLockdrop<Runtime, DispatchValidator, DecodeLimit = ConstU32<8>>(
57    PhantomData<(Runtime, DispatchValidator, DecodeLimit)>,
58);
59
60type CallLengthLimit = ConstU32<2048>;
61
62#[precompile_utils::precompile]
63impl<Runtime, DispatchValidator, DecodeLimit>
64    DispatchLockdrop<Runtime, DispatchValidator, DecodeLimit>
65where
66    Runtime: pallet_evm::Config,
67    <Runtime::RuntimeCall as Dispatchable>::RuntimeOrigin: From<Option<Runtime::AccountId>>,
68    Runtime::RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
69    <Runtime as Config>::AccountId: IsType<AccountId32>,
70    <Runtime as Config>::AccountId: From<[u8; 32]>,
71    DispatchValidator:
72        DispatchValidateT<<Runtime as Config>::AccountId, <Runtime as Config>::RuntimeCall>,
73    DecodeLimit: Get<u32>,
74{
75    #[precompile::public("dispatch_lockdrop_call(bytes,bytes)")]
76    fn dispatch_lockdrop_call(
77        handle: &mut impl PrecompileHandle,
78        call: BoundedBytes<CallLengthLimit>,
79        pubkey: BoundedBytes<ECDSAPublic>,
80    ) -> EvmResult<bool> {
81        log::trace!(
82            target: LOG_TARGET,
83            "raw arguments: call: {call:?}, pubkey: {pubkey:?}",
84        );
85
86        let caller: H160 = handle.context().caller;
87        let input: Vec<u8> = call.into();
88
89        // Record a fixed amount of weight to ensure there is no free execution
90        handle.record_cost(Runtime::GasWeightMapping::weight_to_gas(
91            Weight::from_parts(1_000_000_000u64, 0),
92        ))?;
93
94        // Ensure that the caller matches the public key
95        if caller != Self::get_evm_address_from_pubkey(pubkey.as_bytes()) {
96            let message: &str = "caller does not match the public key";
97            log::trace!(target: LOG_TARGET, "{message}");
98            return Err(revert(message));
99        }
100
101        // Derive the account id from the public key
102        let origin = Self::get_account_id_from_pubkey(pubkey.as_bytes())
103            .ok_or(revert("could not derive AccountId from pubkey"))?;
104
105        // Decode the call
106        let call = Runtime::RuntimeCall::decode_with_depth_limit(DecodeLimit::get(), &mut &*input)
107            .map_err(|_| revert("could not decode call"))?;
108
109        // Validate the call - ensure that the call is allowed in filter
110        DispatchValidator::validate_before_dispatch(&origin, &call)
111            .map_or_else(|| Ok(()), |_| Err(revert("invalid Call")))?;
112
113        // Dispatch the call and handle the cost
114        RuntimeHelper::<Runtime>::try_dispatch::<Runtime::RuntimeCall>(
115            handle,
116            Some(origin).into(),
117            call,
118            0,
119        )?;
120
121        Ok(true)
122    }
123
124    fn get_account_id_from_pubkey(pubkey: &[u8]) -> Option<<Runtime as Config>::AccountId> {
125        libsecp256k1::PublicKey::parse_slice(pubkey, None)
126            .map(|k| sp_io::hashing::blake2_256(k.serialize_compressed().as_ref()).into())
127            .ok()
128    }
129
130    fn get_evm_address_from_pubkey(pubkey: &[u8]) -> H160 {
131        H160::from(H256::from_slice(&keccak_256(pubkey)))
132    }
133}