pallet_schemas/
lib.rs

1//! Schema Management for Message and Stateful Storage
2//!
3//! ## Quick Links
4//! - [Configuration: `Config`](Config)
5//! - [Extrinsics: `Call`](Call)
6//! - [Runtime API: `SchemasRuntimeApi`](../pallet_schemas_runtime_api/trait.SchemasRuntimeApi.html)
7//! - [Custom RPC API: `SchemasApiServer`](../pallet_schemas_rpc/trait.SchemasApiServer.html)
8//! - [Event Enum: `Event`](Event)
9//! - [Error Enum: `Error`](Error)
10#![doc = include_str!("../README.md")]
11//!
12//! ## Implementations
13//!
14//! - [`SchemaValidator`](../common_primitives/schema/trait.SchemaValidator.html): Functions for accessing and validating Schemas. This implementation is what is used in the runtime.
15//! - [`SchemaProvider`](../common_primitives/schema/trait.SchemaProvider.html): Allows another pallet to resolve schema information.
16// Substrate macros are tripping the clippy::expect_used lint.
17#![allow(clippy::expect_used)]
18#![cfg_attr(not(feature = "std"), no_std)]
19#![allow(rustdoc::private_intra_doc_links)]
20// Strong Documentation Lints
21#![deny(
22	rustdoc::broken_intra_doc_links,
23	rustdoc::missing_crate_level_docs,
24	rustdoc::invalid_codeblock_attributes,
25	missing_docs
26)]
27
28extern crate alloc;
29use alloc::{boxed::Box, vec, vec::Vec};
30use common_primitives::{
31	node::ProposalProvider,
32	parquet::ParquetModel,
33	schema::{
34		IntentGroupId, IntentGroupResponse, IntentId, IntentResponse, IntentSetting,
35		IntentSettings, MappedEntityIdentifier, ModelType, NameLookupResponse, PayloadLocation,
36		SchemaId, SchemaInfoResponse, SchemaProvider, SchemaStatus, SchemaValidator,
37		SchemaVersionResponse,
38	},
39};
40use frame_support::{
41	dispatch::{DispatchResult, PostDispatchInfo},
42	ensure,
43	traits::{BuildGenesisConfig, Get},
44};
45use sp_runtime::{traits::Dispatchable, BoundedVec, DispatchError};
46
47#[cfg(test)]
48mod tests;
49
50#[cfg(feature = "runtime-benchmarks")]
51mod benchmarking;
52#[cfg(feature = "runtime-benchmarks")]
53use common_primitives::benchmarks::SchemaBenchmarkHelper;
54use common_primitives::schema::SchemaResponseV2;
55
56mod types;
57
58pub use pallet::*;
59pub mod weights;
60pub use types::*;
61pub use weights::*;
62
63/// Storage migrations
64pub mod migration;
65mod serde;
66
67#[frame_support::pallet]
68pub mod pallet {
69	use super::*;
70	use common_primitives::schema::SchemaResponseV2;
71	use frame_support::pallet_prelude::*;
72	use frame_system::pallet_prelude::*;
73
74	#[pallet::config]
75	pub trait Config: frame_system::Config {
76		/// The overarching event type.
77		#[allow(deprecated)]
78		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
79
80		/// Weight information for extrinsics in this pallet.
81		type WeightInfo: WeightInfo;
82
83		/// Minimum length of Schema model size in bytes
84		#[pallet::constant]
85		type MinSchemaModelSizeBytes: Get<u32>;
86
87		/// Maximum length of a Schema model Bounded Vec
88		#[pallet::constant]
89		type SchemaModelMaxBytesBoundedVecLimit: Get<u32> + MaxEncodedLen;
90
91		/// Maximum number of Intents that can belong to an IntentGroup
92		#[pallet::constant]
93		type MaxIntentsPerIntentGroup: Get<u32>;
94
95		/// The origin that is allowed to create providers via governance
96		type CreateSchemaViaGovernanceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
97
98		/// The runtime call dispatch type.
99		type Proposal: Parameter
100			+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
101			+ From<Call<Self>>;
102
103		/// The Council proposal provider interface
104		type ProposalProvider: ProposalProvider<Self::AccountId, Self::Proposal>;
105
106		/// Maximum number of schema settings that can be registered per schema (if any)
107		#[pallet::constant]
108		type MaxSchemaSettingsPerSchema: Get<u32>;
109	}
110
111	#[pallet::event]
112	#[pallet::generate_deposit(pub (super) fn deposit_event)]
113	pub enum Event<T: Config> {
114		/// Emitted when a schema is registered. [who, schemas id]
115		SchemaCreated {
116			/// Account ID
117			key: T::AccountId,
118
119			/// Schema ID of newly-created schema
120			schema_id: SchemaId,
121		},
122
123		/// Emitted when maximum size for schema model is changed.
124		SchemaMaxSizeChanged {
125			/// Max size of schema document
126			max_size: u32,
127		},
128
129		/// Emitted when a schema is assigned a name
130		SchemaNameCreated {
131			/// Schema ID which a name is assigned
132			schema_id: SchemaId,
133			/// ASCII string in bytes of the assigned name
134			name: Vec<u8>,
135		},
136
137		/// Emitted when an Intent is registered
138		IntentCreated {
139			/// Account ID
140			key: T::AccountId,
141
142			/// IntentId of newly created Intent
143			intent_id: IntentId,
144
145			/// Name of newly created Intent (ASCII bytes)
146			intent_name: Vec<u8>,
147		},
148
149		/// Emitted when an IntentGroup is registered
150		IntentGroupCreated {
151			/// Account ID of creator
152			key: T::AccountId,
153
154			/// IntentGroupId of newly created IntentGroup
155			intent_group_id: IntentGroupId,
156
157			/// Name of newly created IntentGroup (ASCII bytes)
158			intent_group_name: Vec<u8>,
159		},
160
161		/// Emitted when an IntentGroup is updated
162		IntentGroupUpdated {
163			/// Account ID that did the update
164			key: T::AccountId,
165
166			/// IntentGroupId of the IntentGroup that was updated
167			intent_group_id: IntentGroupId,
168		},
169	}
170
171	#[derive(PartialEq, Eq)] // for testing
172	#[pallet::error]
173	pub enum Error<T> {
174		/// Schema is malformed
175		InvalidSchema,
176
177		/// The schema model exceeds the maximum length allowed
178		ExceedsMaxSchemaModelBytes,
179
180		/// The schema model is less than the minimum length allowed
181		LessThanMinSchemaModelBytes,
182
183		/// Attempted to add a new Schema that would cause [`CurrentSchemaIdentifierMaximum`](pallet::storage_types::CurrentSchemaIdentifierMaximum)
184		/// to overflow.
185		SchemaCountOverflow,
186
187		/// Invalid [`IntentSetting`] for Intent or Schema
188		InvalidSetting,
189
190		/// Invalid schema name encoding
191		InvalidSchemaNameEncoding,
192
193		/// Invalid schema name characters
194		InvalidSchemaNameCharacters,
195
196		/// Invalid schema name structure
197		InvalidSchemaNameStructure,
198
199		/// Invalid schema name length
200		InvalidSchemaNameLength,
201
202		/// Invalid schema namespace length
203		InvalidSchemaNamespaceLength,
204
205		/// Invalid schema descriptor length
206		InvalidSchemaDescriptorLength,
207
208		/// Schema version exceeds the maximum allowed number
209		ExceedsMaxNumberOfVersions,
210
211		/// Inserted schema id already exists
212		SchemaIdAlreadyExists,
213
214		///  SchemaId does not exist
215		SchemaIdDoesNotExist,
216
217		/// SchemaId has a name already
218		SchemaIdAlreadyHasName,
219
220		/// Name already exists in the registry
221		NameAlreadyExists,
222
223		/// Attempted to add a new Intent that would cause [`CurrentIntentIdentifierMaximum`](pallet::storage_types::CurrentIntentIdentifierMaximum)
224		/// to overflow.
225		IntentCountOverflow,
226
227		/// The indicated [`IntentId`] does not exist
228		InvalidIntentId,
229
230		/// The indicated [`IntentGroupId`] does not exist
231		InvalidIntentGroupId,
232
233		/// Attempted to add a new IntentGroup that would cause [`CurrentIntentGroupIdentifierMaximum`](pallet::storage_types::CurrentIntentGroupIdentifierMaximum)
234		/// to overflow.
235		IntentGroupCountOverflow,
236
237		/// Too many intents in the group
238		TooManyIntentsInGroup,
239	}
240
241	#[pallet::pallet]
242	#[pallet::storage_version(SCHEMA_STORAGE_VERSION)]
243	pub struct Pallet<T>(_);
244
245	/// Storage for the Governance managed max bytes for schema model
246	/// Allows for altering the max bytes without a full chain upgrade
247	/// - Value: Max Bytes
248	#[pallet::storage]
249	pub(super) type GovernanceSchemaModelMaxBytes<T: Config> = StorageValue<_, u32, ValueQuery>;
250
251	/// Storage type for current number of schemas
252	/// Useful for retrieving latest schema id
253	/// - Value: Last Schema ID
254	#[pallet::storage]
255	pub(super) type CurrentSchemaIdentifierMaximum<T: Config> =
256		StorageValue<_, SchemaId, ValueQuery>;
257
258	/// Storage for message schema info struct data
259	/// - Key: Schema Id
260	/// - Value: [`SchemaInfo`]
261	#[pallet::storage]
262	pub(super) type SchemaInfos<T: Config> =
263		StorageMap<_, Twox64Concat, SchemaId, SchemaInfo, OptionQuery>;
264
265	/// Storage for message schema struct data
266	/// - Key: Schema Id
267	/// - Value: [`BoundedVec`](BoundedVec<T::SchemaModelMaxBytesBoundedVecLimit>)
268	#[pallet::storage]
269	pub(super) type SchemaPayloads<T: Config> =
270		StorageMap<_, Twox64Concat, SchemaId, SchemaPayload<T>, OptionQuery>;
271
272	/// Storage for mapping names to IDs
273	/// - Key: Protocol Name
274	/// - Key: Descriptor
275	/// - Value: [`MappedEntityIdentifier`]
276	#[pallet::storage]
277	pub(super) type NameToMappedEntityIds<T: Config> = StorageDoubleMap<
278		_,
279		Blake2_128Concat,
280		SchemaProtocolName,
281		Blake2_128Concat,
282		SchemaDescriptor,
283		MappedEntityIdentifier,
284		ValueQuery,
285	>;
286
287	/// Storage type for current number of Intents
288	/// Useful for retrieving latest IntentId
289	/// - Value: Last IntentId
290	#[pallet::storage]
291	pub(super) type CurrentIntentIdentifierMaximum<T: Config> =
292		StorageValue<_, IntentId, ValueQuery>;
293
294	/// Storage for Intents
295	/// - Key: [`IntentId`]
296	/// - Value: [`IntentInfo`]
297	#[pallet::storage]
298	pub(super) type IntentInfos<T: Config> =
299		StorageMap<_, Twox64Concat, IntentId, IntentInfo, OptionQuery>;
300
301	/// Storage type for current number of IntentGroups
302	/// Useful for retrieving the latest IntentGroupId
303	/// - Value: Last IntentGroupId
304	#[pallet::storage]
305	pub(super) type CurrentIntentGroupIdentifierMaximum<T: Config> =
306		StorageValue<_, IntentGroupId, ValueQuery>;
307
308	/// Storage for IntentGroups
309	/// - Key: [`IntentGroupId`]
310	/// - Value: [`IntentGroup`](IntentGroup<T>)
311	#[pallet::storage]
312	pub(super) type IntentGroups<T: Config> =
313		StorageMap<_, Twox64Concat, IntentGroupId, IntentGroup<T>, OptionQuery>;
314
315	#[pallet::genesis_config]
316	pub struct GenesisConfig<T: Config> {
317		/// Maximum schema identifier at genesis
318		pub initial_schema_identifier_max: SchemaId,
319		/// Maximum Intent identifier at genesis
320		pub initial_intent_identifier_max: IntentId,
321		/// Maximum IntentGroup identifier at genesis
322		pub initial_intent_group_identifier_max: IntentGroupId,
323		/// Maximum schema size in bytes at genesis
324		pub initial_max_schema_model_size: u32,
325		/// Genesis Intents to load for development
326		pub initial_intents: Vec<GenesisIntent>,
327		/// Genesis Schemas to load for development
328		pub initial_schemas: Vec<GenesisSchema>,
329		/// Genesis IntentGroups to load for development
330		pub initial_intent_groups: Vec<GenesisIntentGroup>,
331		/// Phantom type
332		#[serde(skip)]
333		pub _config: PhantomData<T>,
334	}
335
336	impl<T: Config> core::default::Default for GenesisConfig<T> {
337		fn default() -> Self {
338			Self {
339				initial_intent_identifier_max: 16_000,
340				initial_schema_identifier_max: 16_000,
341				initial_intent_group_identifier_max: 16_000,
342				initial_max_schema_model_size: 1024,
343				initial_intents: Default::default(),
344				initial_schemas: Default::default(),
345				initial_intent_groups: Default::default(),
346				_config: Default::default(),
347			}
348		}
349	}
350
351	impl<T: Config> Into<GenesisConfig<T>> for GenesisSchemasPalletConfig {
352		fn into(self) -> GenesisConfig<T> {
353			GenesisConfig::<T> {
354				initial_intent_identifier_max: self.intent_identifier_max.unwrap_or(16_000),
355				initial_schema_identifier_max: self.schema_identifier_max.unwrap_or(16_000),
356				initial_intent_group_identifier_max: self
357					.intent_group_identifier_max
358					.unwrap_or(16_000),
359				initial_max_schema_model_size: self.max_schema_model_size.unwrap_or(1024),
360				initial_intents: self.intents.unwrap_or_default(),
361				initial_schemas: self.schemas.unwrap_or_default(),
362				initial_intent_groups: self.intent_groups.unwrap_or_default(),
363				_config: Default::default(),
364			}
365		}
366	}
367
368	#[pallet::genesis_build]
369	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
370		fn build(&self) {
371			GovernanceSchemaModelMaxBytes::<T>::put(self.initial_max_schema_model_size);
372
373			for intent in self.initial_intents.iter() {
374				let name_payload: SchemaNamePayload =
375					BoundedVec::try_from(intent.name.clone().into_bytes())
376						.expect("Genesis Intent name larger than max bytes");
377				let parsed_name = SchemaName::try_parse::<T>(name_payload, true)
378					.expect("Bad Genesis Intent name");
379				let settings_vec = BoundedVec::<IntentSetting, T::MaxSchemaSettingsPerSchema>::try_from(intent.settings.clone()).expect("Bad Genesis Intent settings. Perhaps larger than MaxSchemaSettingsPerSchema");
380				let mut settings = IntentSettings::default();
381				settings_vec.iter().for_each(|setting| settings.set(*setting));
382
383				let intent_info =
384					IntentInfo { payload_location: intent.payload_location, settings };
385				Pallet::<T>::store_intent_info(intent.intent_id, intent_info, &parsed_name)
386					.expect("Failed to set Intent in Genesis!");
387			}
388
389			for schema in self.initial_schemas.iter() {
390				let intent = IntentInfos::<T>::try_get(schema.intent_id)
391					.expect("Genesis Schema has no matching Intent.");
392				let model: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit> =
393					BoundedVec::try_from(schema.model.clone().into_bytes()).expect(
394						"Genesis Schema Model larger than SchemaModelMaxBytesBoundedVecLimit",
395					);
396				let schema_info = SchemaInfo {
397					intent_id: schema.intent_id,
398					payload_location: intent.payload_location,
399					model_type: schema.model_type,
400					settings: intent.settings,
401					status: schema.status,
402				};
403
404				Pallet::<T>::store_schema_info_and_payload(schema.schema_id, schema_info, model)
405					.expect("Failed to set Schema in Genesis!");
406			}
407
408			for intent_group in self.initial_intent_groups.iter() {
409				let intent_ids = BoundedVec::try_from(intent_group.intent_ids.clone())
410					.expect("Too many Intents in IntentGroup");
411				intent_ids.iter().for_each(|intent_id| {
412					assert!(
413						IntentInfos::<T>::contains_key(intent_id),
414						"Genesis IntentGroup has no matching Intent."
415					);
416				});
417				let name_payload: SchemaNamePayload = BoundedVec::try_from(
418					intent_group.name.clone().into_bytes(),
419				)
420				.expect("Genesis IntentGroup name larger than {SCHEMA_NAME_BYTES_MAX} bytes}");
421				let parsed_name = SchemaName::try_parse::<T>(name_payload, true)
422					.expect("Bad Genesis IntentGroup name");
423				Pallet::<T>::store_intent_group(
424					intent_group.intent_group_id,
425					intent_ids,
426					&parsed_name,
427				)
428				.expect("Failed to set Schema in Genesis!");
429			}
430
431			// Set the maximums manually
432			CurrentIntentIdentifierMaximum::<T>::put(self.initial_intent_identifier_max);
433			CurrentSchemaIdentifierMaximum::<T>::put(self.initial_schema_identifier_max);
434			CurrentIntentGroupIdentifierMaximum::<T>::put(self.initial_intent_group_identifier_max);
435		}
436	}
437
438	#[pallet::call]
439	impl<T: Config> Pallet<T> {
440		/// REMOVED create_schema() at call index 0
441
442		/// Root and Governance can set a new max value for Schema bytes.
443		/// Must be <= the limit of the Schema BoundedVec used for registration.
444		///
445		/// # Requires
446		/// * Root Origin
447		///
448		/// # Events
449		/// * [`Event::SchemaMaxSizeChanged`]
450		///
451		/// # Errors
452		/// * [`Error::ExceedsMaxSchemaModelBytes`] - Cannot set to above the hard coded maximum [`Config::SchemaModelMaxBytesBoundedVecLimit`]
453		///
454		#[pallet::call_index(1)]
455		#[pallet::weight((T::WeightInfo::set_max_schema_model_bytes(), DispatchClass::Operational))]
456		pub fn set_max_schema_model_bytes(
457			origin: OriginFor<T>,
458			#[pallet::compact] max_size: u32,
459		) -> DispatchResult {
460			ensure_root(origin)?;
461			ensure!(
462				max_size <= T::SchemaModelMaxBytesBoundedVecLimit::get(),
463				Error::<T>::ExceedsMaxSchemaModelBytes
464			);
465			GovernanceSchemaModelMaxBytes::<T>::set(max_size);
466			Self::deposit_event(Event::SchemaMaxSizeChanged { max_size });
467			Ok(())
468		}
469
470		// REMOVED propose_to_create_schema() at call index 2
471		// REMOVED create_schema_via_governance() at call index 3
472		// REMOVED create_schema_v2() at call index 4
473		// REMOVED propose_to_create_schema_v2 at call index 5
474		// REMOVED create_schema_via_governance_v2 at call index 6
475		// REMOVED create_schema_v3 at call index 7
476		// REMOVED propose_to_create_schema_name at call index 8
477		// REMOVED create_schema_name_via_governance at call index 9
478
479		/// Creates a new Intent with a name (testnet)
480		///
481		/// # Events
482		/// * [`Event::IntentCreated`]
483		///
484		/// # Errors
485		/// * [`Error::IntentCountOverflow`] - The Intent count has exceeded its bounds
486		/// * [`Error::InvalidSetting`] - Invalid setting is provided
487		/// * [`Error::NameAlreadyExists`] - The name already exists
488		/// * [`Error::InvalidSchemaNameEncoding`] - The name has an invalid encoding
489		/// * [`Error::InvalidSchemaNameCharacters`] - The name contains invalid characters
490		/// * [`Error::InvalidSchemaNameStructure`] - The name has an invalid structure (i.e., not `protocol.descriptor`)
491		/// * [`Error::InvalidSchemaNameLength`] - The name exceeds the allowed overall name length
492		/// * [`Error::InvalidSchemaNamespaceLength`] - The protocol portion of the name exceeds the max allowed length
493		/// * [`Error::InvalidSchemaDescriptorLength`] - The descriptor portion of the name exceeds the max allowed length
494		///
495		#[pallet::call_index(10)]
496		#[pallet::weight(T::WeightInfo::create_intent(settings.len() as u32))]
497		pub fn create_intent(
498			origin: OriginFor<T>,
499			intent_name: SchemaNamePayload,
500			payload_location: PayloadLocation,
501			settings: BoundedVec<IntentSetting, T::MaxSchemaSettingsPerSchema>,
502		) -> DispatchResult {
503			let sender = ensure_signed(origin)?;
504
505			let (intent_id, parsed_name) =
506				Self::create_intent_for(intent_name, payload_location, settings)?;
507
508			Self::deposit_event(Event::IntentCreated {
509				key: sender,
510				intent_id,
511				intent_name: parsed_name.get_combined_name(),
512			});
513
514			Ok(())
515		}
516
517		/// Create an Intent by means of council approval
518		///
519		/// # Events
520		/// * [`Event::IntentCreated`]
521		///
522		/// # Errors
523		/// * [`Error::IntentCountOverflow`] - The Intent count has exceeded its bounds
524		/// * [`Error::InvalidSetting`] - Invalid setting is provided
525		/// * [`Error::NameAlreadyExists`] - The name already exists
526		/// * [`Error::InvalidSchemaNameEncoding`] - The name has an invalid encoding
527		/// * [`Error::InvalidSchemaNameCharacters`] - The name contains invalid characters
528		/// * [`Error::InvalidSchemaNameStructure`] - The name has an invalid structure (i.e., not `protocol.descriptor`)
529		/// * [`Error::InvalidSchemaNameLength`] - The name exceeds the allowed overall name length
530		/// * [`Error::InvalidSchemaNamespaceLength`] - The protocol portion of the name exceeds the max allowed length
531		/// * [`Error::InvalidSchemaDescriptorLength`] - The descriptor portion of the name exceeds the max allowed length
532		///
533		#[pallet::call_index(11)]
534		#[pallet::weight(T::WeightInfo::create_intent_via_governance(settings.len() as u32))]
535		pub fn create_intent_via_governance(
536			origin: OriginFor<T>,
537			creator_key: T::AccountId,
538			payload_location: PayloadLocation,
539			settings: BoundedVec<IntentSetting, T::MaxSchemaSettingsPerSchema>,
540			intent_name: SchemaNamePayload,
541		) -> DispatchResult {
542			T::CreateSchemaViaGovernanceOrigin::ensure_origin(origin)?;
543			let (intent_id, parsed_name) =
544				Self::create_intent_for(intent_name, payload_location, settings)?;
545
546			Self::deposit_event(Event::IntentCreated {
547				key: creator_key,
548				intent_id,
549				intent_name: parsed_name.get_combined_name(),
550			});
551			Ok(())
552		}
553
554		/// Propose to create an Intent.  Creates a proposal for council approval to create an Intent
555		///
556		#[pallet::call_index(12)]
557		#[pallet::weight(T::WeightInfo::propose_to_create_intent())]
558		pub fn propose_to_create_intent(
559			origin: OriginFor<T>,
560			payload_location: PayloadLocation,
561			settings: BoundedVec<IntentSetting, T::MaxSchemaSettingsPerSchema>,
562			intent_name: SchemaNamePayload,
563		) -> DispatchResult {
564			let proposer = ensure_signed(origin)?;
565
566			let proposal: Box<T::Proposal> = Box::new(
567				(Call::<T>::create_intent_via_governance {
568					creator_key: proposer.clone(),
569					payload_location,
570					settings,
571					intent_name,
572				})
573				.into(),
574			);
575			T::ProposalProvider::propose_with_simple_majority(proposer, proposal)?;
576			Ok(())
577		}
578
579		/// Create an intent group (testnet)
580		///
581		/// # Events
582		/// * [`Event::IntentGroupCreated`]
583		///
584		/// # Errors
585		/// * [`Error::IntentGroupCountOverflow`] - The Intent Group count has exceeded its bounds.
586		/// * [`Error::NameAlreadyExists`] - The name already exists.
587		/// * [`Error::InvalidSchemaNameEncoding`] - The name has an invalid encoding.
588		/// * [`Error::InvalidSchemaNameCharacters`] - The name contains invalid characters.
589		/// * [`Error::InvalidSchemaNameStructure`] - The name has an invalid structure (i.e., not `protocol.descriptor`).
590		/// * [`Error::InvalidSchemaNameLength`] - The name exceeds the allowed overall name length.
591		/// * [`Error::InvalidSchemaNamespaceLength`] - The protocol portion of the name exceeds the max allowed length.
592		/// * [`Error::InvalidSchemaDescriptorLength`] - The descriptor portion of the name exceeds the max allowed length.
593		/// * [`Error::InvalidIntentId`] - At least one of the specified [`IntentId`]s does not exist.
594		///
595		#[pallet::call_index(13)]
596		#[pallet::weight(T::WeightInfo::create_intent_group(intent_ids.len() as u32))]
597		pub fn create_intent_group(
598			origin: OriginFor<T>,
599			intent_group_name: SchemaNamePayload,
600			intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
601		) -> DispatchResult {
602			let sender = ensure_signed(origin)?;
603
604			let (intent_group_id, parsed_name) =
605				Self::create_intent_group_for(intent_group_name, intent_ids)?;
606
607			Self::deposit_event(Event::IntentGroupCreated {
608				key: sender,
609				intent_group_id,
610				intent_group_name: parsed_name.get_combined_name(),
611			});
612			Ok(())
613		}
614
615		/// Create an IntentGroup by means of council approval
616		///
617		/// # Events
618		/// * [`Event::IntentGroupCreated`]
619		///
620		/// # Errors
621		/// * [`Error::IntentGroupCountOverflow`] - The Intent Group count has exceeded its bounds
622		/// * [`Error::NameAlreadyExists`] - The name already exists.
623		/// * [`Error::InvalidSchemaNameEncoding`] - The name has an invalid encoding.
624		/// * [`Error::InvalidSchemaNameCharacters`] - The name contains invalid characters.
625		/// * [`Error::InvalidSchemaNameStructure`] - The name has an invalid structure (i.e., not `protocol.descriptor`).
626		/// * [`Error::InvalidSchemaNameLength`] - The name exceeds the allowed overall name length.
627		/// * [`Error::InvalidSchemaNamespaceLength`] - The protocol portion of the name exceeds the max allowed length.
628		/// * [`Error::InvalidSchemaDescriptorLength`] - The descriptor portion of the name exceeds the max allowed length.
629		/// * [`Error::InvalidIntentId`] - At least one of the specified [`IntentId`]s does not exist
630		///
631		#[pallet::call_index(14)]
632		#[pallet::weight(
633            T::WeightInfo::create_intent_group_via_governance(intent_ids.len() as u32)
634        )]
635		pub fn create_intent_group_via_governance(
636			origin: OriginFor<T>,
637			creator_key: T::AccountId,
638			intent_group_name: SchemaNamePayload,
639			intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
640		) -> DispatchResult {
641			T::CreateSchemaViaGovernanceOrigin::ensure_origin(origin)?;
642			let (intent_group_id, parsed_name) =
643				Self::create_intent_group_for(intent_group_name, intent_ids)?;
644
645			Self::deposit_event(Event::IntentGroupCreated {
646				key: creator_key,
647				intent_group_id,
648				intent_group_name: parsed_name.get_combined_name(),
649			});
650			Ok(())
651		}
652
653		/// Propose to create an Intent Group. Creates a proposal for council approval to create an Intent Group.
654		///
655		#[pallet::call_index(15)]
656		#[pallet::weight(T::WeightInfo::propose_to_create_intent_group())]
657		pub fn propose_to_create_intent_group(
658			origin: OriginFor<T>,
659			intent_group_name: crate::types::SchemaNamePayload,
660			intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
661		) -> DispatchResult {
662			let proposer = ensure_signed(origin)?;
663
664			let proposal: Box<T::Proposal> = Box::new(
665				(Call::<T>::create_intent_group_via_governance {
666					creator_key: proposer.clone(),
667					intent_group_name,
668					intent_ids,
669				})
670				.into(),
671			);
672			T::ProposalProvider::propose_with_simple_majority(proposer, proposal)?;
673			Ok(())
674		}
675
676		/// Update an [`IntentGroup`] (testnet)
677		/// Replaces an IntentGroup's list of Intents with a new list.
678		///
679		/// # Events
680		/// * [`Event::IntentGroupUpdated`]
681		///
682		/// # Errors
683		/// * [`Error::InvalidIntentGroupId`] - The specified [`IntentGroupId`] does not exist.
684		/// * [`Error::InvalidIntentId`] - At least one of the specified [`IntentId`]s does not exist.
685		/// * [`Error::TooManyIntentsInGroup`] - The update would result in too many Intents in the group.
686		#[pallet::call_index(16)]
687		#[pallet::weight(T::WeightInfo::update_intent_group(intent_ids.len() as u32))]
688		pub fn update_intent_group(
689			origin: OriginFor<T>,
690			intent_group_id: IntentGroupId,
691			intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
692		) -> DispatchResult {
693			let sender = ensure_signed(origin)?;
694
695			Self::update_intent_group_for(intent_group_id, intent_ids)?;
696			Self::deposit_event(Event::IntentGroupUpdated { key: sender, intent_group_id });
697			Ok(())
698		}
699
700		/// Update an IntentGroup by means of council approval.
701		/// Replaces an IntentGroup's list of Intents with a new list.
702		///
703		/// # Events
704		/// * [`Event::IntentGroupUpdated`]
705		///
706		/// # Errors
707		/// * [`Error::InvalidIntentGroupId`] - The specified [`IntentGroupId`] does not exist.
708		/// * [`Error::InvalidIntentId`] - At least one of the specified [`IntentId`]s does not exist.
709		/// * [`Error::TooManyIntentsInGroup`] - The update would result in too many Intents in the group.
710		///
711		#[pallet::call_index(17)]
712		#[pallet::weight(
713            T::WeightInfo::update_intent_group_via_governance(intent_ids.len() as u32)
714        )]
715		pub fn update_intent_group_via_governance(
716			origin: OriginFor<T>,
717			updater_key: T::AccountId,
718			intent_group_id: IntentGroupId,
719			intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
720		) -> DispatchResult {
721			T::CreateSchemaViaGovernanceOrigin::ensure_origin(origin)?;
722			ensure!(
723				IntentGroups::<T>::contains_key(intent_group_id),
724				Error::<T>::InvalidIntentGroupId
725			);
726			Self::update_intent_group_for(intent_group_id, intent_ids)?;
727
728			Self::deposit_event(Event::IntentGroupUpdated { key: updater_key, intent_group_id });
729			Ok(())
730		}
731
732		/// Propose to update an Intent Group. Creates a proposal for council approval to update an existing Intent Group.
733		///
734		#[pallet::call_index(18)]
735		#[pallet::weight(T::WeightInfo::propose_to_update_intent_group())]
736		pub fn propose_to_update_intent_group(
737			origin: OriginFor<T>,
738			intent_group_id: IntentGroupId,
739			intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
740		) -> DispatchResult {
741			let proposer = ensure_signed(origin)?;
742
743			let proposal: Box<T::Proposal> = Box::new(
744				(Call::<T>::update_intent_group_via_governance {
745					updater_key: proposer.clone(),
746					intent_group_id,
747					intent_ids,
748				})
749				.into(),
750			);
751			T::ProposalProvider::propose_with_simple_majority(proposer, proposal)?;
752			Ok(())
753		}
754
755		/// Propose to create a schema.  Creates a proposal for council approval to create a schema
756		///
757		#[pallet::call_index(19)]
758		#[pallet::weight(T::WeightInfo::propose_to_create_schema_v3())]
759		pub fn propose_to_create_schema_v3(
760			origin: OriginFor<T>,
761			intent_id: IntentId,
762			model: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit>,
763			model_type: ModelType,
764		) -> DispatchResult {
765			let proposer = ensure_signed(origin)?;
766
767			let proposal: Box<T::Proposal> = Box::new(
768				(Call::<T>::create_schema_via_governance_v3 {
769					creator_key: proposer.clone(),
770					intent_id,
771					model,
772					model_type,
773				})
774				.into(),
775			);
776			T::ProposalProvider::propose_with_simple_majority(proposer, proposal)?;
777			Ok(())
778		}
779
780		/// Create a schema by means of council approval
781		///
782		/// # Events
783		/// * [`Event::SchemaCreated`]
784		///
785		/// # Errors
786		/// * [`Error::LessThanMinSchemaModelBytes`] - The schema's length is less than the minimum schema length
787		/// * [`Error::ExceedsMaxSchemaModelBytes`] - The schema's length is greater than the maximum schema length
788		/// * [`Error::InvalidSchema`] - Schema is malformed in some way
789		/// * [`Error::SchemaCountOverflow`] - The schema count has exceeded its bounds
790		///
791		#[pallet::call_index(20)]
792		#[pallet::weight(T::WeightInfo::create_schema_via_governance_v3(model.len() as u32))]
793		pub fn create_schema_via_governance_v3(
794			origin: OriginFor<T>,
795			creator_key: T::AccountId,
796			intent_id: IntentId,
797			model: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit>,
798			model_type: ModelType,
799		) -> DispatchResult {
800			T::CreateSchemaViaGovernanceOrigin::ensure_origin(origin)?;
801			let schema_id = Self::create_schema_for(intent_id, model, model_type)?;
802
803			Self::deposit_event(Event::SchemaCreated { key: creator_key, schema_id });
804			Ok(())
805		}
806
807		/// Adds a given schema to storage. (testnet)
808		///
809		/// The schema in question must be of length
810		/// between the min and max model size allowed for schemas (see pallet
811		/// constants above). If the pallet's maximum schema limit has been
812		/// fulfilled by the time this extrinsic is called, a SchemaCountOverflow error
813		/// will be thrown.
814		///
815		/// # Events
816		/// * [`Event::SchemaCreated`]
817		///
818		/// # Errors
819		/// * [`Error::LessThanMinSchemaModelBytes`] - The schema's length is less than the minimum schema length
820		/// * [`Error::ExceedsMaxSchemaModelBytes`] - The schema's length is greater than the maximum schema length
821		/// * [`Error::InvalidSchema`] - Schema is malformed in some way
822		/// * [`Error::SchemaCountOverflow`] - The schema count has exceeded its bounds
823		///
824		#[pallet::call_index(21)]
825		#[pallet::weight(T::WeightInfo::create_schema_v4(model.len() as u32))]
826		pub fn create_schema_v4(
827			origin: OriginFor<T>,
828			intent_id: IntentId,
829			model: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit>,
830			model_type: ModelType,
831		) -> DispatchResult {
832			let sender = ensure_signed(origin)?;
833
834			let schema_id = Self::create_schema_for(intent_id, model, model_type)?;
835
836			Self::deposit_event(Event::SchemaCreated { key: sender, schema_id });
837			Ok(())
838		}
839	}
840
841	impl<T: Config> Pallet<T> {
842		/// Set the maximum schema id to the supplied value.
843		#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
844		pub fn set_schema_count(n: SchemaId) {
845			<CurrentSchemaIdentifierMaximum<T>>::set(n);
846		}
847
848		/// Set the maximum intent id to the supplied value.
849		#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
850		pub fn set_intent_count(n: IntentId) {
851			<CurrentIntentIdentifierMaximum<T>>::set(n);
852		}
853
854		/// Lowest-level insertion function for a [`SchemaInfo`] and [`SchemaPayload`] into storage,
855		/// using an already-allocated [`SchemaId`]
856		pub fn store_schema_info_and_payload(
857			schema_id: SchemaId,
858			schema_info: SchemaInfo,
859			schema_payload: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit>,
860		) -> Result<(), DispatchError> {
861			<SchemaInfos<T>>::insert(schema_id, schema_info);
862			<SchemaPayloads<T>>::insert(schema_id, schema_payload);
863			Ok(())
864		}
865
866		/// Inserts both the [`SchemaInfo`] and Schema Payload into storage
867		/// Updates the [`CurrentSchemaIdentifierMaximum`] storage
868		pub fn add_schema(
869			intent_id: IntentId,
870			model: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit>,
871			model_type: ModelType,
872			payload_location: PayloadLocation,
873			settings: IntentSettings,
874			status: SchemaStatus,
875		) -> Result<SchemaId, DispatchError> {
876			let schema_id = Self::get_next_schema_id()?;
877			let schema_info =
878				SchemaInfo { intent_id, model_type, payload_location, settings, status };
879			<CurrentSchemaIdentifierMaximum<T>>::set(schema_id);
880			Self::store_schema_info_and_payload(schema_id, schema_info, model)?;
881
882			Ok(schema_id)
883		}
884
885		/// Lowest-level insertion function for a [`IntentInfo`] into storage,
886		/// using an already-allocated [`IntentId`]
887		pub fn store_intent_info(
888			intent_id: IntentId,
889			intent_info: IntentInfo,
890			intent_name: &SchemaName,
891		) -> Result<(), DispatchError> {
892			NameToMappedEntityIds::<T>::insert(
893				&intent_name.namespace,
894				&intent_name.descriptor,
895				MappedEntityIdentifier::Intent(intent_id),
896			);
897			<IntentInfos<T>>::insert(intent_id, intent_info);
898
899			Ok(())
900		}
901
902		/// Inserts the [`IntentInfo`] into storage
903		/// Updates the [`CurrentIntentIdentifierMaximum`] storage
904		/// Does little validation, as this is an internal method intended to be called
905		/// by higher-level extrinsics that perform various validations.
906		///
907		/// # Errors
908		/// * [`Error::IntentCountOverflow`]
909		pub fn add_intent(
910			payload_location: PayloadLocation,
911			settings: BoundedVec<IntentSetting, T::MaxSchemaSettingsPerSchema>,
912			intent_name: &SchemaName,
913		) -> Result<IntentId, DispatchError> {
914			let intent_id = Self::get_next_intent_id()?;
915			let mut set_settings = IntentSettings::all_disabled();
916			if !settings.is_empty() {
917				for i in settings.into_inner() {
918					set_settings.set(i);
919				}
920			}
921
922			let intent_info = IntentInfo { payload_location, settings: set_settings };
923			<CurrentIntentIdentifierMaximum<T>>::set(intent_id);
924			Self::store_intent_info(intent_id, intent_info, intent_name)?;
925
926			Ok(intent_id)
927		}
928
929		/// Lowest-level insertion function for a [`IntentGroup`] into storage,
930		/// using an already-allocated [`IntentGroupId`]
931		pub fn store_intent_group(
932			intent_group_id: IntentGroupId,
933			intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
934			intent_group_name: &SchemaName,
935		) -> Result<(), DispatchError> {
936			NameToMappedEntityIds::<T>::insert(
937				&intent_group_name.namespace,
938				&intent_group_name.descriptor,
939				MappedEntityIdentifier::IntentGroup(intent_group_id),
940			);
941			Self::update_intent_group_storage(intent_group_id, intent_ids)?;
942
943			Ok(())
944		}
945
946		/// Inserts a list of [`IntentId`]s with a new [`IntentGroupId`] in storage, and adds.
947		/// a new name mapping to the [`IntentGroupId`].
948		/// Updates the [`CurrentIntentGroupIdentifierMaximum`] storage.
949		/// Does little validation, as this is an internal method intended to be called by.
950		/// higher-level extrinsics that perform the validations.
951		///
952		/// Errors
953		/// * [`Error::IntentGroupCountOverflow`]
954		pub fn add_intent_group(
955			intent_group_name: &SchemaName,
956			intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
957		) -> Result<IntentGroupId, DispatchError> {
958			let intent_group_id = Self::get_next_intent_group_id()?;
959
960			<CurrentIntentGroupIdentifierMaximum<T>>::set(intent_group_id);
961			Self::store_intent_group(intent_group_id, intent_ids, intent_group_name)?;
962			Ok(intent_group_id)
963		}
964
965		/// Updates the list of [`IntentId`]s associated with a given [`IntentGroupId`]
966		/// Does little validation, as this is an internal method intended to be called by
967		/// higher-level extrinsics that perform the validations.
968		///
969		pub fn update_intent_group_storage(
970			intent_group_id: IntentGroupId,
971			intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
972		) -> DispatchResult {
973			IntentGroups::<T>::set(intent_group_id, Some(IntentGroup { intent_ids }));
974			Ok(())
975		}
976
977		/// Retrieve a schema by id
978		pub fn get_schema_by_id(schema_id: SchemaId) -> Option<SchemaResponseV2> {
979			match (SchemaInfos::<T>::get(schema_id), SchemaPayloads::<T>::get(schema_id)) {
980				(Some(schema_info), Some(payload)) => {
981					let model_vec: Vec<u8> = payload.into_inner();
982					let response = SchemaResponseV2 {
983						schema_id,
984						intent_id: schema_info.intent_id,
985						model: model_vec,
986						model_type: schema_info.model_type,
987						payload_location: schema_info.payload_location,
988						settings: schema_info.settings.0.iter().collect::<Vec<IntentSetting>>(),
989						status: schema_info.status,
990					};
991					Some(response)
992				},
993				(None, Some(_)) | (Some(_), None) => {
994					log::error!("Corrupted state for schema {schema_id:?}, Should never happen!");
995					None
996				},
997				(None, None) => None,
998			}
999		}
1000
1001		/// Retrieve a schema info by id
1002		pub fn get_schema_info_by_id(schema_id: SchemaId) -> Option<SchemaInfoResponse> {
1003			if let Some(schema_info) = SchemaInfos::<T>::get(schema_id) {
1004				let response = SchemaInfoResponse {
1005					schema_id,
1006					intent_id: schema_info.intent_id,
1007					model_type: schema_info.model_type,
1008					payload_location: schema_info.payload_location,
1009					settings: schema_info.settings.0.iter().collect::<Vec<IntentSetting>>(),
1010					status: schema_info.status,
1011				};
1012				return Some(response);
1013			}
1014			None
1015		}
1016
1017		/// Retrieve an Intent by its ID
1018		pub fn get_intent_by_id(intent_id: IntentId) -> Option<IntentResponse> {
1019			IntentInfos::<T>::get(intent_id).map(|intent_info| IntentResponse {
1020				intent_id,
1021				payload_location: intent_info.payload_location,
1022				settings: intent_info.settings.0.iter().collect::<Vec<IntentSetting>>(),
1023				schema_ids: None,
1024			})
1025		}
1026
1027		/// Retrieve an Intent by its ID
1028		/// NOTE: This must not be called on-chain due to inability to determine weight
1029		pub fn get_intent_by_id_with_schemas(intent_id: IntentId) -> Option<IntentResponse> {
1030			let response = Self::get_intent_by_id(intent_id);
1031			response.map(|mut intent| {
1032				intent.schema_ids = Some(
1033					SchemaInfos::<T>::iter()
1034						.filter(|(_, info)| info.intent_id == intent_id)
1035						.map(|(id, _)| id)
1036						.collect::<Vec<SchemaId>>(),
1037				);
1038				intent
1039			})
1040		}
1041
1042		/// Retrieve an IntentGroup by its ID
1043		pub fn get_intent_group_by_id(
1044			intent_group_id: IntentGroupId,
1045		) -> Option<IntentGroupResponse> {
1046			if let Some(intent_group) = IntentGroups::<T>::get(intent_group_id) {
1047				return Some(IntentGroupResponse {
1048					intent_group_id,
1049					intent_ids: intent_group.intent_ids.into(),
1050				});
1051			}
1052			None
1053		}
1054
1055		/// Ensures that a given u8 Vector conforms to a recognized Parquet shape
1056		///
1057		/// # Errors
1058		/// * [`Error::InvalidSchema`]
1059		/// * [`Error::SchemaCountOverflow`]
1060		///
1061		pub fn ensure_valid_model(
1062			model_type: &ModelType,
1063			model: &BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit>,
1064		) -> DispatchResult {
1065			match *model_type {
1066				ModelType::Parquet => {
1067					serde_json::from_slice::<ParquetModel>(model)
1068						.map_err(|_| Error::<T>::InvalidSchema)?;
1069				},
1070				ModelType::AvroBinary => serde::validate_json_model(model.clone().into_inner())
1071					.map_err(|_| Error::<T>::InvalidSchema)?,
1072			};
1073			Ok(())
1074		}
1075
1076		/// Get the next available schema id
1077		///
1078		/// # Errors
1079		/// * [`Error::SchemaCountOverflow`]
1080		///
1081		fn get_next_schema_id() -> Result<SchemaId, DispatchError> {
1082			let next = CurrentSchemaIdentifierMaximum::<T>::get()
1083				.checked_add(1)
1084				.ok_or(Error::<T>::SchemaCountOverflow)?;
1085
1086			Ok(next)
1087		}
1088
1089		/// Get the next available [`IntentId`]
1090		///
1091		/// # Errors
1092		/// * [`Error::IntentCountOverflow`]
1093		///
1094		fn get_next_intent_id() -> Result<IntentId, DispatchError> {
1095			let next = CurrentIntentIdentifierMaximum::<T>::get()
1096				.checked_add(1)
1097				.ok_or(Error::<T>::IntentCountOverflow)?;
1098
1099			Ok(next)
1100		}
1101
1102		/// Get the next available [`IntentGroupId`]
1103		///
1104		/// Errors
1105		/// * [`Error::IntentGroupCountOverflow`]
1106		///
1107		fn get_next_intent_group_id() -> Result<IntentGroupId, DispatchError> {
1108			let next = CurrentIntentGroupIdentifierMaximum::<T>::get()
1109				.checked_add(1)
1110				.ok_or(Error::<T>::IntentGroupCountOverflow)?;
1111			Ok(next)
1112		}
1113
1114		/// Adds a given schema to storage. The schema in question must be of length
1115		/// between the min and max model size allowed for schemas (see pallet
1116		/// constants above). If the pallet's maximum schema limit has been
1117		/// fulfilled by the time this extrinsic is called, a SchemaCountOverflow error
1118		/// will be thrown.
1119		///
1120		/// # Errors
1121		/// * [`Error::LessThanMinSchemaModelBytes`] - The schema's length is less than the minimum schema length
1122		/// * [`Error::ExceedsMaxSchemaModelBytes`] - The schema's length is greater than the maximum schema length
1123		/// * [`Error::InvalidSchema`] - Schema is malformed in some way
1124		/// * [`Error::SchemaCountOverflow`] - The schema count has exceeded its bounds
1125		/// * [`Error::InvalidSetting`] - Invalid setting is provided
1126		pub fn create_schema_for(
1127			intent_id: IntentId,
1128			model: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit>,
1129			model_type: ModelType,
1130		) -> Result<SchemaId, DispatchError> {
1131			Self::ensure_valid_model(&model_type, &model)?;
1132			ensure!(
1133				model.len() >= T::MinSchemaModelSizeBytes::get() as usize,
1134				Error::<T>::LessThanMinSchemaModelBytes
1135			);
1136			ensure!(
1137				model.len() <= GovernanceSchemaModelMaxBytes::<T>::get() as usize,
1138				Error::<T>::ExceedsMaxSchemaModelBytes
1139			);
1140			let intent_info =
1141				IntentInfos::<T>::get(intent_id).ok_or(Error::<T>::InvalidIntentId)?;
1142			let schema_id = Self::add_schema(
1143				intent_id,
1144				model,
1145				model_type,
1146				intent_info.payload_location,
1147				intent_info.settings,
1148				SchemaStatus::Active,
1149			)?;
1150			Ok(schema_id)
1151		}
1152
1153		/// Adds a given Intent to storage. If the pallet's maximum Intent limit has been
1154		/// fulfilled by the time this extrinsic is called, an IntentCountOverflow error
1155		/// will be thrown.
1156		///
1157		/// # Errors
1158		/// * [`Error::IntentCountOverflow`] - The Intent count has exceeded its bounds
1159		/// * [`Error::InvalidSetting`] - Invalid setting is provided
1160		/// * [`Error::NameAlreadyExists`] - The name already exists
1161		/// * [`Error::InvalidSchemaNameEncoding`] - The name has an invalid encoding
1162		/// * [`Error::InvalidSchemaNameCharacters`] - The name contains invalid characters
1163		/// * [`Error::InvalidSchemaNameStructure`] - The name has an invalid structure (i.e., not `protocol.descriptor`)
1164		/// * [`Error::InvalidSchemaNameLength`] - The name exceed the allowed overall name length
1165		/// * [`Error::InvalidSchemaNamespaceLength`] - The protocol portion of the name exceeds the max allowed length
1166		/// * [`Error::InvalidSchemaDescriptorLength`] - The descriptor portion of the name exceeds the max allowed length
1167		pub fn create_intent_for(
1168			intent_name_payload: SchemaNamePayload,
1169			payload_location: PayloadLocation,
1170			settings: BoundedVec<IntentSetting, T::MaxSchemaSettingsPerSchema>,
1171		) -> Result<(IntentId, SchemaName), DispatchError> {
1172			// AppendOnly is only valid for Itemized payload location
1173			ensure!(
1174				!settings.contains(&IntentSetting::AppendOnly) ||
1175					payload_location == PayloadLocation::Itemized,
1176				Error::<T>::InvalidSetting
1177			);
1178			// SignatureRequired is only valid for Itemized and Paginated payload locations
1179			ensure!(
1180				!settings.contains(&IntentSetting::SignatureRequired) ||
1181					payload_location == PayloadLocation::Itemized ||
1182					payload_location == PayloadLocation::Paginated,
1183				Error::<T>::InvalidSetting
1184			);
1185			let parsed_name = Self::parse_and_verify_new_name(&intent_name_payload)?;
1186			let intent_id = Self::add_intent(payload_location, settings, &parsed_name)?;
1187			Ok((intent_id, parsed_name))
1188		}
1189
1190		/// Adds a given Intent to storage. If the pallet's maximum Intent limit has been
1191		/// fulfilled by the time this extrinsic is called, an IntentCountOverflow error
1192		/// will be thrown.
1193		///
1194		/// # Errors
1195		/// * [`Error::IntentGroupCountOverflow`] - The schema count has exceeded its bounds
1196		/// * [`Error::NameAlreadyExists`] - The name already exists
1197		/// * [`Error::InvalidSchemaNameEncoding`] - The name has an invalid encoding
1198		/// * [`Error::InvalidSchemaNameCharacters`] - The name contains invalid characters
1199		/// * [`Error::InvalidSchemaNameStructure`] - The name has an invalid structure (i.e., not `protocol.descriptor`)
1200		/// * [`Error::InvalidSchemaNameLength`] - The name exceed the allowed overall name length
1201		/// * [`Error::InvalidSchemaNamespaceLength`] - The protocol portion of the name exceeds the max allowed length
1202		/// * [`Error::InvalidSchemaDescriptorLength`] - The descriptor portion of the name exceeds the max allowed length
1203		/// * [`Error::InvalidIntentId`] - One of the [`IntentId`]s specified does not exist
1204		pub fn create_intent_group_for(
1205			intent_group_name: SchemaNamePayload,
1206			intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
1207		) -> Result<(IntentGroupId, SchemaName), DispatchError> {
1208			let intent_group_name = Self::parse_and_verify_new_name(&intent_group_name)?;
1209			Self::validate_intent_ids(&intent_ids)?;
1210			let intent_id = Self::add_intent_group(&intent_group_name, intent_ids)?;
1211			Ok((intent_id, intent_group_name))
1212		}
1213
1214		/// Update the list of [`IntentId`]s associated with a given [`IntentGroupId`]
1215		///
1216		/// # Errors
1217		/// * [`Error::InvalidIntentGroupId`] - The specified [`IntentGroupId`] doesn't exist
1218		/// * [`Error::InvalidIntentId`] - One of the [`IntentId`]s specified doesn't exist
1219		pub fn update_intent_group_for(
1220			intent_group_id: IntentGroupId,
1221			intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
1222		) -> Result<(), DispatchError> {
1223			ensure!(
1224				IntentGroups::<T>::contains_key(intent_group_id),
1225				Error::<T>::InvalidIntentGroupId
1226			);
1227			Self::validate_intent_ids(&intent_ids)?;
1228			Self::update_intent_group_storage(intent_group_id, intent_ids)?;
1229			Ok(())
1230		}
1231
1232		/// Validate that all items in a list of [`IntentId`]s exist
1233		///
1234		/// # Errors
1235		/// * [`Error::InvalidIntentId`] - At least one of the specified [`IntentId`]s does not exist
1236		///
1237		pub fn validate_intent_ids(
1238			intent_ids: &BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
1239		) -> Result<(), DispatchError> {
1240			let max_intent_id = CurrentIntentIdentifierMaximum::<T>::get();
1241			intent_ids.iter().try_for_each::<_, _>(|intent_id| {
1242				ensure!(*intent_id <= max_intent_id, Error::<T>::InvalidIntentId);
1243				Ok::<(), DispatchError>(())
1244			})?;
1245			Ok(())
1246		}
1247
1248		/// a method to return all versions of a schema name with their schemaIds
1249		/// Warning: Must only get called from RPC, since the number of DB accesses is not deterministic
1250		pub fn get_schema_versions(schema_name: Vec<u8>) -> Option<Vec<SchemaVersionResponse>> {
1251			if let Some(mut entities) = Self::get_intent_or_group_ids_by_name(schema_name.clone()) {
1252				entities.retain(|e| matches!(e.entity_id, MappedEntityIdentifier::Intent(_)));
1253				return Some(
1254					entities
1255						.iter()
1256						.flat_map(|e| {
1257							let schema_ids: Vec<SchemaId> = SchemaInfos::<T>::iter()
1258								.filter_map(|(id, info)| match e.entity_id {
1259									MappedEntityIdentifier::Intent(intent_id) => {
1260										if info.intent_id == intent_id {
1261											Some(id)
1262										} else {
1263											None
1264										}
1265									},
1266									_ => None,
1267								})
1268								.collect();
1269							schema_ids.convert_to_response(&e.name)
1270						})
1271						.collect::<Vec<SchemaVersionResponse>>(),
1272				);
1273			}
1274			None
1275		}
1276
1277		/// method to return the entity ID (Intent or IntentGroup) associated with a name
1278		/// Warning: Must only get called from RPC, since the number of DB accesses is not deterministic
1279		pub fn get_intent_or_group_ids_by_name(
1280			entity_name: Vec<u8>,
1281		) -> Option<Vec<NameLookupResponse>> {
1282			let parsed_name =
1283				FullyQualifiedName::try_parse::<T>(BoundedVec::try_from(entity_name).ok()?, false)
1284					.ok()?;
1285
1286			if parsed_name.descriptor_exists() {
1287				if let Ok(id) = NameToMappedEntityIds::<T>::try_get(
1288					&parsed_name.namespace,
1289					&parsed_name.descriptor,
1290				) {
1291					return Some(vec![id.convert_to_response(&parsed_name)]);
1292				}
1293				return None;
1294			}
1295
1296			let responses: Vec<NameLookupResponse> =
1297				NameToMappedEntityIds::<T>::iter_prefix(&parsed_name.namespace)
1298					.map(|(descriptor, val)| {
1299						val.convert_to_response(&parsed_name.new_with_descriptor(descriptor))
1300					})
1301					.collect();
1302
1303			(!responses.is_empty()).then_some(responses)
1304		}
1305
1306		/// Parses and validates a new name and makes sure it does not already exist
1307		/// # Errors
1308		/// * [`Error::NameAlreadyExists`] - The name already exists
1309		/// * [`Error::InvalidSchemaNameEncoding`] - The name has an invalid encoding
1310		/// * [`Error::InvalidSchemaNameCharacters`] - The name contains invalid characters
1311		/// * [`Error::InvalidSchemaNameStructure`] - The name has an invalid structure (i.e., not `protocol.descriptor`)
1312		/// * [`Error::InvalidSchemaNameLength`] - The name exceed the allowed overall name length
1313		/// * [`Error::InvalidSchemaNamespaceLength`] - The protocol portion of the name exceeds the max allowed length
1314		/// * [`Error::InvalidSchemaDescriptorLength`] - The descriptor portion of the name exceeds the max allowed length
1315		fn parse_and_verify_new_name(
1316			name: &SchemaNamePayload,
1317		) -> Result<SchemaName, DispatchError> {
1318			let parsed_name = SchemaName::try_parse::<T>(name.clone(), true)?;
1319
1320			ensure!(
1321				!NameToMappedEntityIds::<T>::contains_key(
1322					&parsed_name.namespace,
1323					&parsed_name.descriptor,
1324				),
1325				Error::<T>::NameAlreadyExists,
1326			);
1327
1328			Ok(parsed_name)
1329		}
1330	}
1331}
1332
1333#[allow(clippy::unwrap_used)]
1334#[cfg(feature = "runtime-benchmarks")]
1335impl<T: Config> SchemaBenchmarkHelper for Pallet<T> {
1336	/// Sets schema count.
1337	fn set_schema_count(schema_id: SchemaId) {
1338		Self::set_schema_count(schema_id);
1339	}
1340
1341	/// Sets Intent count.
1342	fn set_intent_count(intent_id: IntentId) {
1343		Self::set_intent_count(intent_id)
1344	}
1345
1346	/// Creates a schema.
1347	fn create_schema(
1348		intent_id: IntentId,
1349		model: Vec<u8>,
1350		model_type: ModelType,
1351		payload_location: PayloadLocation,
1352	) -> Result<SchemaId, DispatchError> {
1353		let model: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit> =
1354			model.try_into().unwrap();
1355		Self::ensure_valid_model(&model_type, &model)?;
1356		let schema_id = Self::add_schema(
1357			intent_id,
1358			model,
1359			model_type,
1360			payload_location,
1361			IntentSettings::default(),
1362			SchemaStatus::Active,
1363		)?;
1364		Ok(schema_id)
1365	}
1366
1367	/// Creates an Intent
1368	fn create_intent(
1369		name_payload: Vec<u8>,
1370		payload_location: PayloadLocation,
1371		settings: Vec<IntentSetting>,
1372	) -> Result<IntentId, DispatchError> {
1373		let name = BoundedVec::try_from(name_payload).unwrap();
1374		let (intent_id, _) =
1375			Self::create_intent_for(name, payload_location, settings.try_into().unwrap())?;
1376		Ok(intent_id)
1377	}
1378}
1379
1380impl<T: Config> SchemaValidator<SchemaId> for Pallet<T> {
1381	fn are_all_intent_ids_valid(intent_ids: &[IntentId]) -> bool {
1382		let latest_issue_intent_id = CurrentIntentIdentifierMaximum::<T>::get();
1383		intent_ids.iter().all(|id| id <= &latest_issue_intent_id)
1384	}
1385
1386	#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
1387	fn set_schema_count(n: SchemaId) {
1388		Self::set_schema_count(n);
1389	}
1390
1391	#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
1392	fn set_intent_count(n: IntentId) {
1393		Self::set_intent_count(n);
1394	}
1395}
1396
1397impl<T: Config> SchemaProvider<SchemaId> for Pallet<T> {
1398	fn get_schema_by_id(schema_id: SchemaId) -> Option<SchemaResponseV2> {
1399		Self::get_schema_by_id(schema_id)
1400	}
1401
1402	fn get_schema_info_by_id(schema_id: SchemaId) -> Option<SchemaInfoResponse> {
1403		Self::get_schema_info_by_id(schema_id)
1404	}
1405
1406	fn get_intent_by_id(intent_id: IntentId) -> Option<IntentResponse> {
1407		Self::get_intent_by_id(intent_id)
1408	}
1409}