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

            
21
use core::marker::PhantomData;
22
use cumulus_primitives_core::relay_chain::BlockNumber as RelayBlockNumber;
23
use fp_evm::{PrecompileFailure, PrecompileHandle};
24
use frame_support::{ensure, traits::ConstU32};
25
use pallet_precompile_benchmarks::WeightInfo as TWeightInfo;
26
use precompile_utils::prelude::*;
27
use sp_core::H256;
28
use sp_std::vec::Vec;
29
use storage_proof_primitives::*;
30

            
31
#[cfg(test)]
32
mod mock;
33
#[cfg(test)]
34
mod tests;
35

            
36
pub const CALL_DATA_LIMIT: u32 = 2u32.pow(16);
37
pub const ARRAY_LIMIT: u32 = 2048;
38
pub const KEY_LENGTH_LIMIT: u32 = 256;
39

            
40
pub type GetCallDataLimit = ConstU32<CALL_DATA_LIMIT>;
41
pub type GetKeyLengthLimit = ConstU32<KEY_LENGTH_LIMIT>;
42
pub type GetArrayLimit = ConstU32<ARRAY_LIMIT>;
43

            
44
pub type RawKey = BoundedBytes<GetKeyLengthLimit>;
45

            
46
/// Relay Data Verifier precompile.
47
pub struct RelayDataVerifierPrecompile<Runtime, WeightInfo>(PhantomData<(Runtime, WeightInfo)>);
48

            
49
90
#[precompile_utils::precompile]
50
impl<Runtime, WeightInfo> RelayDataVerifierPrecompile<Runtime, WeightInfo>
51
where
52
	Runtime: frame_system::Config
53
		+ pallet_relay_storage_roots::Config
54
		+ pallet_evm::Config
55
		+ pallet_precompile_benchmarks::Config,
56
	WeightInfo: TWeightInfo,
57
{
58
	/// Verify the storage entry using the provided relay block number and proof. Return the value
59
	/// of the storage entry if the proof is valid and the entry exists.
60
	#[precompile::public("verifyEntry(uint32,(bytes32,bytes[]),bytes)")]
61
	#[precompile::public("verify_entry(uint32,(bytes32,bytes[]),bytes)")]
62
4
	fn verify_entry(
63
4
		handle: &mut impl PrecompileHandle,
64
4
		relay_block_number: RelayBlockNumber,
65
4
		proof: ReadProof,
66
4
		key: RawKey,
67
4
	) -> EvmResult<UnboundedBytes> {
68
4
		// Charge gas for storage proof verification
69
4
		let weight = WeightInfo::verify_entry(proof.proof.len() as u32);
70
4
		handle.record_external_cost(Some(weight.ref_time()), Some(0), Some(0))?;
71

            
72
		// Get the storage root of the relay block
73
4
		let storage_root = Self::get_storage_root(handle, relay_block_number)?;
74

            
75
		// One read per key
76
3
		handle.record_cost(RuntimeHelper::<Runtime>::db_read_gas_cost())?;
77

            
78
		// Read and return the value associated ton the key
79
3
		verify_entry(storage_root, proof.to_raw_proof(), key.as_bytes())
80
3
			.map_err(map_err)
81
3
			.map(UnboundedBytes::from)
82
4
	}
83

            
84
	/// Verify the storage entries using the provided relay block number and proof. Return the
85
	/// values of the storage entries in the same order of keys, if the proof is valid and the
86
	/// entries exist.
87
	#[precompile::public("verifyEntries(uint32,(bytes32,bytes[]),bytes[])")]
88
	#[precompile::public("verify_entries(uint32,(bytes32,bytes[]),bytes[])")]
89
5
	fn verify_entries(
90
5
		handle: &mut impl PrecompileHandle,
91
5
		relay_block_number: RelayBlockNumber,
92
5
		proof: ReadProof,
93
5
		keys: BoundedVec<RawKey, GetArrayLimit>,
94
5
	) -> EvmResult<BoundedVec<UnboundedBytes, GetArrayLimit>> {
95
5
		ensure!(keys.len() > 0, revert("Keys must not be empty"));
96

            
97
		//  Charge gas for storage proof verification
98
4
		let weight = WeightInfo::verify_entry(proof.proof.len() as u32);
99
4
		handle.record_external_cost(Some(weight.ref_time()), Some(0), Some(0))?;
100

            
101
		// Get the storage root of the relay block
102
4
		let storage_root = Self::get_storage_root(handle, relay_block_number)?;
103

            
104
		// Charge one db read per key
105
3
		handle.record_cost(
106
3
			(keys.len() as u64).saturating_mul(RuntimeHelper::<Runtime>::db_read_gas_cost()),
107
3
		)?;
108

            
109
		// Read and return the values associated ton the keys
110
3
		let keys = Vec::from(keys);
111
8
		let keys: Vec<_> = keys.iter().map(|x| x.as_bytes()).collect();
112
3
		verify_entries(storage_root, proof.to_raw_proof(), &keys)
113
3
			.map_err(map_err)
114
3
			.map(|x| x.into_iter().map(UnboundedBytes::from).collect::<Vec<_>>())
115
3
			.map(|x| BoundedVec::from(x))
116
5
	}
117

            
118
	#[precompile::public("latestRelayBlockNumber()")]
119
	#[precompile::public("latest_relay_block_number()")]
120
	#[precompile::view]
121
4
	fn latest_relay_block(handle: &mut impl PrecompileHandle) -> EvmResult<RelayBlockNumber> {
122
4
		let weight = WeightInfo::latest_relay_block();
123
4
		handle.record_external_cost(Some(weight.ref_time()), Some(weight.proof_size()), Some(0))?;
124

            
125
4
		pallet_relay_storage_roots::RelayStorageRootKeys::<Runtime>::get()
126
4
			.last()
127
4
			.cloned()
128
4
			.ok_or(revert("No relay block found"))
129
4
	}
130

            
131
8
	fn get_storage_root(
132
8
		handle: &mut impl PrecompileHandle,
133
8
		relay_block_number: RelayBlockNumber,
134
8
	) -> EvmResult<H256> {
135
8
		handle.record_db_read::<Runtime>(84)?;
136
8
		pallet_relay_storage_roots::RelayStorageRoot::<Runtime>::get(relay_block_number)
137
8
			.ok_or(revert("Block number not present"))
138
8
	}
139
}
140

            
141
4
fn map_err(err: ProofError) -> PrecompileFailure {
142
4
	match err {
143
2
		ProofError::RootMismatch => revert("Root Mismatch"),
144
		ProofError::Proof => revert("Invalid Proof"),
145
2
		ProofError::Absent => revert("Value is not present"),
146
		ProofError::BlockNumberNotPresent => revert("Block number not present"),
147
	}
148
4
}
149

            
150
11
#[derive(Clone, Debug, solidity::Codec)]
151
pub struct ReadProof {
152
	// Block Hash used to generate the proof
153
	pub at: H256,
154
	// A storage proof
155
	pub proof: BoundedVec<BoundedBytes<GetCallDataLimit>, GetArrayLimit>,
156
}
157

            
158
impl ReadProof {
159
6
	pub fn to_raw_proof(self) -> Vec<Vec<u8>> {
160
6
		Vec::from(self.proof)
161
6
			.iter()
162
48
			.map(|x| x.as_bytes().to_vec())
163
6
			.collect()
164
6
	}
165
}