pallet_evm_precompile_relay_verifier/
lib.rs

1// Copyright 2025 Moonbeam Foundation.
2// This file is part of Moonbeam.
3
4// Moonbeam is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Moonbeam is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Moonbeam.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Precompile for verifying relay entries against a relay block number.
18
19#![cfg_attr(not(feature = "std"), no_std)]
20
21use core::marker::PhantomData;
22use cumulus_primitives_core::relay_chain::BlockNumber as RelayBlockNumber;
23use fp_evm::{PrecompileFailure, PrecompileHandle};
24use frame_support::{ensure, traits::ConstU32};
25use pallet_precompile_benchmarks::WeightInfo as TWeightInfo;
26use precompile_utils::prelude::*;
27use sp_core::H256;
28use sp_std::vec::Vec;
29use storage_proof_primitives::*;
30
31#[cfg(test)]
32mod mock;
33#[cfg(test)]
34mod tests;
35
36pub const CALL_DATA_LIMIT: u32 = 2u32.pow(16);
37pub const ARRAY_LIMIT: u32 = 2048;
38pub const KEY_LENGTH_LIMIT: u32 = 256;
39
40pub type GetCallDataLimit = ConstU32<CALL_DATA_LIMIT>;
41pub type GetKeyLengthLimit = ConstU32<KEY_LENGTH_LIMIT>;
42pub type GetArrayLimit = ConstU32<ARRAY_LIMIT>;
43
44pub type RawKey = BoundedBytes<GetKeyLengthLimit>;
45
46/// Relay Data Verifier precompile.
47pub struct RelayDataVerifierPrecompile<Runtime, WeightInfo>(PhantomData<(Runtime, WeightInfo)>);
48
49#[precompile_utils::precompile]
50impl<Runtime, WeightInfo> RelayDataVerifierPrecompile<Runtime, WeightInfo>
51where
52	Runtime: frame_system::Config + pallet_relay_storage_roots::Config + pallet_evm::Config,
53	WeightInfo: TWeightInfo,
54{
55	/// Verify the storage entry using the provided relay block number and proof. Return the value
56	/// of the storage entry if the proof is valid and the entry exists.
57	#[precompile::public("verifyEntry(uint32,(bytes32,bytes[]),bytes)")]
58	#[precompile::public("verify_entry(uint32,(bytes32,bytes[]),bytes)")]
59	fn verify_entry(
60		handle: &mut impl PrecompileHandle,
61		relay_block_number: RelayBlockNumber,
62		proof: ReadProof,
63		key: RawKey,
64	) -> EvmResult<UnboundedBytes> {
65		// Charge gas for storage proof verification
66		let weight = WeightInfo::verify_entry(proof.proof.len() as u32);
67		handle.record_external_cost(Some(weight.ref_time()), Some(0), Some(0))?;
68
69		// Get the storage root of the relay block
70		let storage_root = Self::get_storage_root(handle, relay_block_number)?;
71
72		// One read per key
73		handle.record_cost(RuntimeHelper::<Runtime>::db_read_gas_cost())?;
74
75		// Read and return the value associated ton the key
76		verify_entry(storage_root, proof.to_raw_proof(), key.as_bytes())
77			.map_err(map_err)
78			.map(UnboundedBytes::from)
79	}
80
81	/// Verify the storage entries using the provided relay block number and proof. Return the
82	/// values of the storage entries in the same order of keys, if the proof is valid and the
83	/// entries exist.
84	#[precompile::public("verifyEntries(uint32,(bytes32,bytes[]),bytes[])")]
85	#[precompile::public("verify_entries(uint32,(bytes32,bytes[]),bytes[])")]
86	fn verify_entries(
87		handle: &mut impl PrecompileHandle,
88		relay_block_number: RelayBlockNumber,
89		proof: ReadProof,
90		keys: BoundedVec<RawKey, GetArrayLimit>,
91	) -> EvmResult<BoundedVec<UnboundedBytes, GetArrayLimit>> {
92		ensure!(keys.len() > 0, revert("Keys must not be empty"));
93
94		//  Charge gas for storage proof verification
95		let weight = WeightInfo::verify_entry(proof.proof.len() as u32);
96		handle.record_external_cost(Some(weight.ref_time()), Some(0), Some(0))?;
97
98		// Get the storage root of the relay block
99		let storage_root = Self::get_storage_root(handle, relay_block_number)?;
100
101		// Charge one db read per key
102		handle.record_cost(
103			(keys.len() as u64).saturating_mul(RuntimeHelper::<Runtime>::db_read_gas_cost()),
104		)?;
105
106		// Read and return the values associated ton the keys
107		let keys = Vec::from(keys);
108		let keys: Vec<_> = keys.iter().map(|x| x.as_bytes()).collect();
109		verify_entries(storage_root, proof.to_raw_proof(), &keys)
110			.map_err(map_err)
111			.map(|x| x.into_iter().map(UnboundedBytes::from).collect::<Vec<_>>())
112			.map(|x| BoundedVec::from(x))
113	}
114
115	#[precompile::public("latestRelayBlockNumber()")]
116	#[precompile::public("latest_relay_block_number()")]
117	#[precompile::view]
118	fn latest_relay_block(handle: &mut impl PrecompileHandle) -> EvmResult<RelayBlockNumber> {
119		let weight = WeightInfo::latest_relay_block();
120		handle.record_external_cost(Some(weight.ref_time()), Some(weight.proof_size()), Some(0))?;
121
122		pallet_relay_storage_roots::RelayStorageRootKeys::<Runtime>::get()
123			.last()
124			.cloned()
125			.ok_or(revert("No relay block found"))
126	}
127
128	fn get_storage_root(
129		handle: &mut impl PrecompileHandle,
130		relay_block_number: RelayBlockNumber,
131	) -> EvmResult<H256> {
132		handle.record_db_read::<Runtime>(84)?;
133		pallet_relay_storage_roots::RelayStorageRoot::<Runtime>::get(relay_block_number)
134			.ok_or(revert("Block number not present"))
135	}
136}
137
138fn map_err(err: ProofError) -> PrecompileFailure {
139	match err {
140		ProofError::RootMismatch => revert("Root Mismatch"),
141		ProofError::Proof => revert("Invalid Proof"),
142		ProofError::Absent => revert("Value is not present"),
143		ProofError::BlockNumberNotPresent => revert("Block number not present"),
144	}
145}
146
147#[derive(Clone, Debug, solidity::Codec)]
148pub struct ReadProof {
149	// Block Hash used to generate the proof
150	pub at: H256,
151	// A storage proof
152	pub proof: BoundedVec<BoundedBytes<GetCallDataLimit>, GetArrayLimit>,
153}
154
155impl ReadProof {
156	pub fn to_raw_proof(self) -> Vec<Vec<u8>> {
157		Vec::from(self.proof)
158			.iter()
159			.map(|x| x.as_bytes().to_vec())
160			.collect()
161	}
162}