pallet_evm_precompile_call_permit/
lib.rs1#![cfg_attr(not(feature = "std"), no_std)]
18
19use core::marker::PhantomData;
20use evm::ExitReason;
21use fp_evm::{Context, ExitRevert, PrecompileFailure, PrecompileHandle, Transfer};
22use frame_support::{
23 ensure,
24 storage::types::{StorageMap, ValueQuery},
25 traits::{ConstU32, Get, StorageInstance, Time},
26 Blake2_128Concat,
27};
28use precompile_utils::{evm::costs::call_cost, prelude::*};
29use sp_core::{H160, H256, U256};
30use sp_io::hashing::keccak_256;
31use sp_runtime::traits::UniqueSaturatedInto;
32use sp_std::vec::Vec;
33
34#[cfg(test)]
35mod mock;
36#[cfg(test)]
37mod tests;
38
39pub struct Nonces;
41
42impl StorageInstance for Nonces {
43 const STORAGE_PREFIX: &'static str = "Nonces";
44
45 fn pallet_prefix() -> &'static str {
46 "PrecompileCallPermit"
47 }
48}
49
50pub type NoncesStorage = StorageMap<
52 Nonces,
53 Blake2_128Concat,
55 H160,
56 U256,
58 ValueQuery,
59>;
60
61pub const PERMIT_TYPEHASH: [u8; 32] = keccak256!(
63 "CallPermit(address from,address to,uint256 value,bytes data,uint64 gaslimit\
64,uint256 nonce,uint256 deadline)"
65);
66
67const PERMIT_DOMAIN: [u8; 32] = keccak256!(
69 "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
70);
71
72pub const CALL_DATA_LIMIT: u32 = 2u32.pow(16);
73
74pub struct CallPermitPrecompile<Runtime>(PhantomData<Runtime>);
78
79#[precompile_utils::precompile]
80impl<Runtime> CallPermitPrecompile<Runtime>
81where
82 Runtime: pallet_evm::Config,
83{
84 fn compute_domain_separator(address: H160) -> [u8; 32] {
85 let name: H256 = keccak_256(b"Call Permit Precompile").into();
86 let version: H256 = keccak256!("1").into();
87 let chain_id: U256 = Runtime::ChainId::get().into();
88
89 let domain_separator_inner = solidity::encode_arguments((
90 H256::from(PERMIT_DOMAIN),
91 name,
92 version,
93 chain_id,
94 Address(address),
95 ));
96
97 keccak_256(&domain_separator_inner).into()
98 }
99
100 pub fn generate_permit(
101 address: H160,
102 from: H160,
103 to: H160,
104 value: U256,
105 data: Vec<u8>,
106 gaslimit: u64,
107 nonce: U256,
108 deadline: U256,
109 ) -> [u8; 32] {
110 let domain_separator = Self::compute_domain_separator(address);
111
112 let permit_content = solidity::encode_arguments((
113 H256::from(PERMIT_TYPEHASH),
114 Address(from),
115 Address(to),
116 value,
117 H256::from(keccak_256(&data)),
119 gaslimit,
120 nonce,
121 deadline,
122 ));
123 let permit_content = keccak_256(&permit_content);
124 let mut pre_digest = Vec::with_capacity(2 + 32 + 32);
125 pre_digest.extend_from_slice(b"\x19\x01");
126 pre_digest.extend_from_slice(&domain_separator);
127 pre_digest.extend_from_slice(&permit_content);
128 keccak_256(&pre_digest)
129 }
130
131 pub fn dispatch_inherent_cost() -> u64 {
132 3_000 + RuntimeHelper::<Runtime>::db_write_gas_cost() }
135
136 #[precompile::public(
137 "dispatch(address,address,uint256,bytes,uint64,uint256,uint8,bytes32,bytes32)"
138 )]
139 fn dispatch(
140 handle: &mut impl PrecompileHandle,
141 from: Address,
142 to: Address,
143 value: U256,
144 data: BoundedBytes<ConstU32<CALL_DATA_LIMIT>>,
145 gas_limit: u64,
146 deadline: U256,
147 v: u8,
148 r: H256,
149 s: H256,
150 ) -> EvmResult<UnboundedBytes> {
151 handle.record_db_read::<Runtime>(8)?;
153 handle.record_db_read::<Runtime>(104)?;
155
156 handle.record_cost(Self::dispatch_inherent_cost())?;
157
158 let from: H160 = from.into();
159 let to: H160 = to.into();
160 let data: Vec<u8> = data.into();
161
162 let call_cost = call_cost(value, <Runtime as pallet_evm::Config>::config());
164
165 let total_cost = gas_limit
166 .checked_add(call_cost)
167 .ok_or_else(|| revert("Call require too much gas (uint64 overflow)"))?;
168
169 if total_cost > handle.remaining_gas() {
170 return Err(revert("Gaslimit is too low to dispatch provided call"));
171 }
172
173 let timestamp: u128 =
177 <Runtime as pallet_evm::Config>::Timestamp::now().unique_saturated_into();
178 let timestamp: U256 = U256::from(timestamp / 1000);
179
180 ensure!(deadline >= timestamp, revert("Permit expired"));
181
182 let nonce = NoncesStorage::get(from);
183
184 let permit = Self::generate_permit(
185 handle.context().address,
186 from,
187 to,
188 value,
189 data.clone(),
190 gas_limit,
191 nonce,
192 deadline,
193 );
194
195 let mut sig = [0u8; 65];
196 sig[0..32].copy_from_slice(&r.as_bytes());
197 sig[32..64].copy_from_slice(&s.as_bytes());
198 sig[64] = v;
199
200 let signer = sp_io::crypto::secp256k1_ecdsa_recover(&sig, &permit)
201 .map_err(|_| revert("Invalid permit"))?;
202 let signer = H160::from(H256::from_slice(keccak_256(&signer).as_slice()));
203
204 ensure!(
205 signer != H160::zero() && signer == from,
206 revert("Invalid permit")
207 );
208
209 NoncesStorage::insert(from, nonce + U256::one());
210
211 let sub_context = Context {
213 caller: from,
214 address: to.clone(),
215 apparent_value: value,
216 };
217
218 let transfer = if value.is_zero() {
219 None
220 } else {
221 Some(Transfer {
222 source: from,
223 target: to.clone(),
224 value,
225 })
226 };
227
228 let (reason, output) =
229 handle.call(to, transfer, data, Some(gas_limit), false, &sub_context);
230 match reason {
231 ExitReason::Error(exit_status) => Err(PrecompileFailure::Error { exit_status }),
232 ExitReason::Fatal(exit_status) => Err(PrecompileFailure::Fatal { exit_status }),
233 ExitReason::Revert(_) => Err(PrecompileFailure::Revert {
234 exit_status: ExitRevert::Reverted,
235 output,
236 }),
237 ExitReason::Succeed(_) => Ok(output.into()),
238 }
239 }
240
241 #[precompile::public("nonces(address)")]
242 #[precompile::view]
243 fn nonces(handle: &mut impl PrecompileHandle, owner: Address) -> EvmResult<U256> {
244 handle.record_db_read::<Runtime>(104)?;
246
247 let owner: H160 = owner.into();
248
249 let nonce = NoncesStorage::get(owner);
250
251 Ok(nonce)
252 }
253
254 #[precompile::public("DOMAIN_SEPARATOR()")]
255 #[precompile::view]
256 fn domain_separator(handle: &mut impl PrecompileHandle) -> EvmResult<H256> {
257 handle.record_db_read::<Runtime>(8)?;
259
260 let domain_separator: H256 =
261 Self::compute_domain_separator(handle.context().address).into();
262
263 Ok(domain_separator)
264 }
265}