Skip to main content

pallet_encointer_reputation_commitments/
lib.rs

1// Copyright (c) 2019 Alain Brenzikofer
2// This file is part of Encointer
3//
4// Encointer 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// Encointer 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 Encointer.  If not, see <http://www.gnu.org/licenses/>.
16
17#![cfg_attr(not(feature = "std"), no_std)]
18
19use core::marker::PhantomData;
20use encointer_primitives::{
21	communities::CommunityIdentifier,
22	reputation_commitments::{DescriptorType, PurposeIdType},
23	scheduler::{CeremonyIndexType, CeremonyPhaseType},
24};
25use frame_system::{self as frame_system, ensure_signed, pallet_prelude::OriginFor};
26use log::info;
27pub use pallet::*;
28use pallet_encointer_scheduler::OnCeremonyPhaseChange;
29use sp_core::H256;
30use sp_std::convert::TryInto;
31pub use weights::WeightInfo;
32
33// Logger target
34const LOG: &str = "encointer";
35
36#[cfg(feature = "runtime-benchmarks")]
37mod benchmarking;
38#[cfg(test)]
39mod mock;
40#[cfg(test)]
41mod tests;
42
43mod weights;
44#[frame_support::pallet]
45pub mod pallet {
46	use super::*;
47	use frame_support::pallet_prelude::*;
48
49	#[pallet::pallet]
50	pub struct Pallet<T>(PhantomData<T>);
51
52	#[pallet::config]
53	pub trait Config:
54		frame_system::Config
55		+ pallet_encointer_ceremonies::Config
56		+ pallet_encointer_communities::Config
57	{
58		#[allow(deprecated)]
59		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
60		type WeightInfo: WeightInfo;
61	}
62
63	#[pallet::call]
64	impl<T: Config> Pallet<T> {
65		#[pallet::call_index(0)]
66		#[pallet::weight((<T as Config>::WeightInfo::register_purpose(), DispatchClass::Normal, Pays::Yes))]
67		pub fn register_purpose(
68			origin: OriginFor<T>,
69			descriptor: DescriptorType,
70		) -> DispatchResultWithPostInfo {
71			ensure_signed(origin)?;
72			Self::do_register_purpose(descriptor)?;
73			Ok(().into())
74		}
75
76		#[pallet::call_index(1)]
77		#[pallet::weight((<T as Config>::WeightInfo::commit_reputation(), DispatchClass::Normal, Pays::Yes))]
78		pub fn commit_reputation(
79			origin: OriginFor<T>,
80			cid: CommunityIdentifier,
81			cindex: CeremonyIndexType,
82			purpose: PurposeIdType,
83			commitment_hash: Option<H256>,
84		) -> DispatchResultWithPostInfo {
85			let from = ensure_signed(origin)?;
86			Self::do_commit_reputation(&from, cid, cindex, purpose, commitment_hash)?;
87			Ok(().into())
88		}
89	}
90
91	impl<T: Config> Pallet<T> {
92		pub fn do_register_purpose(descriptor: DescriptorType) -> Result<PurposeIdType, Error<T>> {
93			let current_id = Self::current_purpose_id();
94			let next_id = current_id.checked_add(1).ok_or(<Error<T>>::PurposeRegistryOverflow)?;
95
96			<CurrentPurposeId<T>>::put(next_id);
97			<Purposes<T>>::insert(current_id, descriptor.clone());
98			info!(target: LOG, "commitment purpose registered: {current_id:?}, {descriptor:?}");
99			Self::deposit_event(Event::RegisteredCommitmentPurpose(current_id, descriptor));
100			Ok(current_id)
101		}
102
103		pub fn do_commit_reputation(
104			account: &T::AccountId,
105			cid: CommunityIdentifier,
106			cindex: CeremonyIndexType,
107			purpose: PurposeIdType,
108			commitment_hash: Option<H256>,
109		) -> Result<(), Error<T>> {
110			if !<Purposes<T>>::contains_key(purpose) {
111				return Err(<Error<T>>::InexistentPurpose);
112			}
113
114			if !<pallet_encointer_ceremonies::Pallet<T>>::participant_reputation(
115				(cid, cindex),
116				account,
117			)
118			.is_verified()
119			{
120				return Err(<Error<T>>::NoReputation);
121			}
122
123			if <Commitments<T>>::contains_key((cid, cindex), (purpose, &account)) {
124				return Err(<Error<T>>::AlreadyCommitted);
125			}
126
127			<Commitments<T>>::insert((cid, cindex), (purpose, &account), commitment_hash);
128			info!(
129				target: LOG,
130				"{:?} commited reputation for cid {:?} at cindex {:?} for purposed id {:?}",
131				account.clone(),
132				cid,
133				cindex,
134				purpose
135			);
136			Self::deposit_event(Event::CommitedReputation(
137				cid,
138				cindex,
139				purpose,
140				account.clone(),
141				commitment_hash,
142			));
143			Ok(())
144		}
145
146		#[allow(deprecated)]
147		pub fn purge_registry(cindex: CeremonyIndexType) {
148			let cids = <pallet_encointer_communities::Pallet<T>>::community_identifiers();
149			for cid in cids.into_iter() {
150				<Commitments<T>>::remove_prefix((cid, cindex), None);
151			}
152			info!(target: LOG, "commitment registry purged at cindex {cindex:?}");
153			Self::deposit_event(Event::CommitmentRegistryPurged(cindex));
154		}
155	}
156
157	#[pallet::event]
158	#[pallet::generate_deposit(pub(super) fn deposit_event)]
159	pub enum Event<T: Config> {
160		/// commitment purpose registered
161		RegisteredCommitmentPurpose(PurposeIdType, DescriptorType),
162		/// reputation commited for purpose
163		CommitedReputation(
164			CommunityIdentifier,
165			CeremonyIndexType,
166			PurposeIdType,
167			T::AccountId,
168			Option<H256>,
169		),
170		/// Commitment registry purged
171		CommitmentRegistryPurged(CeremonyIndexType),
172	}
173
174	#[pallet::error]
175	#[derive(PartialEq)]
176	pub enum Error<T> {
177		/// Participant already commited their reputation for this purpose
178		AlreadyCommitted,
179		/// Participant does not have reputation for the specified cid, cindex
180		NoReputation,
181		/// Purposose registry is full
182		PurposeRegistryOverflow,
183		/// Inexsitent purpose
184		InexistentPurpose,
185	}
186
187	#[pallet::storage]
188	#[pallet::getter(fn current_purpose_id)]
189	pub(super) type CurrentPurposeId<T: Config> = StorageValue<_, PurposeIdType, ValueQuery>;
190
191	#[pallet::storage]
192	#[pallet::getter(fn purposes)]
193	pub(super) type Purposes<T: Config> =
194		StorageMap<_, Identity, PurposeIdType, DescriptorType, ValueQuery>;
195
196	#[pallet::storage]
197	#[pallet::getter(fn commitments)]
198	pub(super) type Commitments<T: Config> = StorageDoubleMap<
199		_,
200		Blake2_128Concat,
201		(CommunityIdentifier, CeremonyIndexType),
202		Identity,
203		(PurposeIdType, T::AccountId),
204		Option<H256>,
205		ValueQuery,
206	>;
207}
208
209impl<T: Config> OnCeremonyPhaseChange for Pallet<T> {
210	fn on_ceremony_phase_change(new_phase: CeremonyPhaseType) {
211		match new_phase {
212			CeremonyPhaseType::Assigning => {},
213			CeremonyPhaseType::Attesting => {},
214			CeremonyPhaseType::Registering => {
215				let reputation_lifetime =
216					<pallet_encointer_ceremonies::Pallet<T>>::reputation_lifetime();
217				let cindex = <pallet_encointer_scheduler::Pallet<T>>::current_ceremony_index();
218				// Clean up with a time delay, such that participants can claim their UBI in the
219				// following cycle.
220				if cindex > reputation_lifetime {
221					Self::purge_registry(
222						cindex.saturating_sub(reputation_lifetime).saturating_sub(1),
223					);
224				}
225			},
226		}
227	}
228}