1#![doc = include_str!("../README.md")]
11#![allow(clippy::expect_used)]
13#![cfg_attr(not(feature = "std"), no_std)]
15#![deny(
17 rustdoc::broken_intra_doc_links,
18 rustdoc::missing_crate_level_docs,
19 rustdoc::invalid_codeblock_attributes,
20 missing_docs
21)]
22
23#[cfg(feature = "runtime-benchmarks")]
24mod benchmarking;
25#[cfg(test)]
26mod tests;
27
28pub mod weights;
29
30mod types;
31
32use core::{convert::TryInto, fmt::Debug};
33use frame_support::{ensure, pallet_prelude::Weight, traits::Get, BoundedVec};
34use sp_runtime::DispatchError;
35
36extern crate alloc;
37use alloc::vec::Vec;
38use common_primitives::{
39 messages::*,
40 msa::{
41 DelegatorId, MessageSourceId, MsaLookup, MsaValidator, ProviderId, SchemaGrantValidator,
42 },
43 schema::*,
44};
45use frame_support::dispatch::DispatchResult;
46use parity_scale_codec::Encode;
47
48#[cfg(feature = "runtime-benchmarks")]
49use common_primitives::benchmarks::{MsaBenchmarkHelper, SchemaBenchmarkHelper};
50
51pub use pallet::*;
52pub use types::*;
53pub use weights::*;
54
55use cid::Cid;
56use frame_system::pallet_prelude::*;
57
58#[frame_support::pallet]
59pub mod pallet {
60 use super::*;
61 use frame_support::pallet_prelude::*;
62
63 pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
65
66 #[pallet::config]
67 pub trait Config: frame_system::Config {
68 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
70
71 type WeightInfo: WeightInfo;
73
74 type MsaInfoProvider: MsaLookup + MsaValidator<AccountId = Self::AccountId>;
76
77 type SchemaGrantValidator: SchemaGrantValidator<BlockNumberFor<Self>>;
79
80 type SchemaProvider: SchemaProvider<SchemaId>;
82
83 #[pallet::constant]
85 type MessagesMaxPayloadSizeBytes: Get<u32> + Clone + Debug + MaxEncodedLen;
86
87 #[cfg(feature = "runtime-benchmarks")]
88 type MsaBenchmarkHelper: MsaBenchmarkHelper<Self::AccountId>;
90
91 #[cfg(feature = "runtime-benchmarks")]
92 type SchemaBenchmarkHelper: SchemaBenchmarkHelper;
94 }
95
96 #[pallet::pallet]
97 #[pallet::storage_version(STORAGE_VERSION)]
98 pub struct Pallet<T>(_);
99
100 #[pallet::storage]
103 #[pallet::whitelist_storage]
104 pub(super) type BlockMessageIndex<T: Config> = StorageValue<_, MessageIndex, ValueQuery>;
105
106 #[pallet::storage]
107 pub(super) type MessagesV2<T: Config> = StorageNMap<
108 _,
109 (
110 storage::Key<Twox64Concat, BlockNumberFor<T>>,
111 storage::Key<Twox64Concat, SchemaId>,
112 storage::Key<Twox64Concat, MessageIndex>,
113 ),
114 Message<T::MessagesMaxPayloadSizeBytes>,
115 OptionQuery,
116 >;
117
118 #[pallet::error]
119 pub enum Error<T> {
120 TooManyMessagesInBlock,
122
123 ExceedsMaxMessagePayloadSizeBytes,
125
126 TypeConversionOverflow,
128
129 InvalidMessageSourceAccount,
131
132 InvalidSchemaId,
134
135 UnAuthorizedDelegate,
137
138 InvalidPayloadLocation,
140
141 UnsupportedCidVersion,
143
144 InvalidCid,
146 }
147
148 #[pallet::event]
149 #[pallet::generate_deposit(pub(super) fn deposit_event)]
150 pub enum Event<T: Config> {
151 MessagesStored {
154 schema_id: SchemaId,
156 block_number: BlockNumberFor<T>,
158 },
159 MessagesInBlock,
161 }
162
163 #[pallet::hooks]
164 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
165 fn on_initialize(_current: BlockNumberFor<T>) -> Weight {
166 <BlockMessageIndex<T>>::set(0u16);
167 T::DbWeight::get().reads(1u64).saturating_add(T::DbWeight::get().writes(1u64))
169 }
171 }
172
173 #[pallet::call]
174 impl<T: Config> Pallet<T> {
175 #[pallet::call_index(0)]
194 #[pallet::weight(T::WeightInfo::add_ipfs_message())]
195 pub fn add_ipfs_message(
196 origin: OriginFor<T>,
197 #[pallet::compact] schema_id: SchemaId,
198 cid: Vec<u8>,
199 #[pallet::compact] payload_length: u32,
200 ) -> DispatchResult {
201 let provider_key = ensure_signed(origin)?;
202 let cid_binary = Self::validate_cid(&cid)?;
203 let payload_tuple: OffchainPayloadType = (cid_binary, payload_length);
204 let bounded_payload: BoundedVec<u8, T::MessagesMaxPayloadSizeBytes> = payload_tuple
205 .encode()
206 .try_into()
207 .map_err(|_| Error::<T>::ExceedsMaxMessagePayloadSizeBytes)?;
208
209 if let Some(schema) = T::SchemaProvider::get_schema_info_by_id(schema_id) {
210 ensure!(
211 schema.payload_location == PayloadLocation::IPFS,
212 Error::<T>::InvalidPayloadLocation
213 );
214
215 let provider_msa_id = Self::find_msa_id(&provider_key)?;
216 let current_block = frame_system::Pallet::<T>::block_number();
217 if Self::add_message(
218 provider_msa_id,
219 None,
220 bounded_payload,
221 schema_id,
222 current_block,
223 )? {
224 Self::deposit_event(Event::MessagesInBlock);
225 }
226 Ok(())
227 } else {
228 Err(Error::<T>::InvalidSchemaId.into())
229 }
230 }
231
232 #[pallet::call_index(1)]
246 #[pallet::weight(T::WeightInfo::add_onchain_message(payload.len() as u32))]
247 pub fn add_onchain_message(
248 origin: OriginFor<T>,
249 on_behalf_of: Option<MessageSourceId>,
250 #[pallet::compact] schema_id: SchemaId,
251 payload: Vec<u8>,
252 ) -> DispatchResult {
253 let provider_key = ensure_signed(origin)?;
254
255 let bounded_payload: BoundedVec<u8, T::MessagesMaxPayloadSizeBytes> =
256 payload.try_into().map_err(|_| Error::<T>::ExceedsMaxMessagePayloadSizeBytes)?;
257
258 if let Some(schema) = T::SchemaProvider::get_schema_info_by_id(schema_id) {
259 ensure!(
260 schema.payload_location == PayloadLocation::OnChain,
261 Error::<T>::InvalidPayloadLocation
262 );
263
264 let provider_msa_id = Self::find_msa_id(&provider_key)?;
265 let provider_id = ProviderId(provider_msa_id);
266
267 let current_block = frame_system::Pallet::<T>::block_number();
268 let maybe_delegator = match on_behalf_of {
270 Some(delegator_msa_id) => {
271 let delegator_id = DelegatorId(delegator_msa_id);
272 T::SchemaGrantValidator::ensure_valid_schema_grant(
273 provider_id,
274 delegator_id,
275 schema_id,
276 current_block,
277 )
278 .map_err(|_| Error::<T>::UnAuthorizedDelegate)?;
279 delegator_id
280 },
281 None => DelegatorId(provider_msa_id), };
283
284 if Self::add_message(
285 provider_msa_id,
286 Some(maybe_delegator.into()),
287 bounded_payload,
288 schema_id,
289 current_block,
290 )? {
291 Self::deposit_event(Event::MessagesInBlock);
292 }
293
294 Ok(())
295 } else {
296 Err(Error::<T>::InvalidSchemaId.into())
297 }
298 }
299 }
300}
301
302impl<T: Config> Pallet<T> {
303 pub fn add_message(
309 provider_msa_id: MessageSourceId,
310 msa_id: Option<MessageSourceId>,
311 payload: BoundedVec<u8, T::MessagesMaxPayloadSizeBytes>,
312 schema_id: SchemaId,
313 current_block: BlockNumberFor<T>,
314 ) -> Result<bool, DispatchError> {
315 let index = BlockMessageIndex::<T>::get();
316 let first = index == 0;
317 let msg = Message {
318 payload, provider_msa_id,
320 msa_id,
321 };
322
323 <MessagesV2<T>>::insert((current_block, schema_id, index), msg);
324 BlockMessageIndex::<T>::set(index.saturating_add(1));
325 Ok(first)
326 }
327
328 pub fn find_msa_id(key: &T::AccountId) -> Result<MessageSourceId, DispatchError> {
335 Ok(T::MsaInfoProvider::ensure_valid_msa_key(key)
336 .map_err(|_| Error::<T>::InvalidMessageSourceAccount)?)
337 }
338
339 pub fn get_messages_by_schema_and_block(
346 schema_id: SchemaId,
347 schema_payload_location: PayloadLocation,
348 block_number: BlockNumberFor<T>,
349 ) -> Vec<MessageResponse> {
350 let block_number_value: u32 = block_number.try_into().unwrap_or_default();
351
352 match schema_payload_location {
353 PayloadLocation::Itemized | PayloadLocation::Paginated => Vec::new(),
354 _ => {
355 let mut messages: Vec<_> = <MessagesV2<T>>::iter_prefix((block_number, schema_id))
356 .map(|(index, msg)| {
357 msg.map_to_response(block_number_value, schema_payload_location, index)
358 })
359 .collect();
360 messages.sort_by(|a, b| a.index.cmp(&b.index));
361 messages
362 },
363 }
364 }
365
366 pub fn validate_cid(in_cid: &[u8]) -> Result<Vec<u8>, DispatchError> {
373 let cid_str: &str = core::str::from_utf8(in_cid).map_err(|_| Error::<T>::InvalidCid)?;
375 ensure!(cid_str.len() > 2, Error::<T>::InvalidCid);
376 ensure!(!cid_str.starts_with("Qm"), Error::<T>::UnsupportedCidVersion);
378
379 let cid_b = multibase::decode(cid_str).map_err(|_| Error::<T>::InvalidCid)?.1;
381 ensure!(Cid::read_bytes(&cid_b[..]).is_ok(), Error::<T>::InvalidCid);
382
383 Ok(cid_b)
384 }
385}