Refactoring Checkpoint: (WIP)
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
//! Benchmarking setup for pezpallet-identity-kyc
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use super::*;
|
||||
use crate::Pallet as IdentityKyc;
|
||||
use pezframe_benchmarking::v2::*;
|
||||
use pezframe_support::traits::Currency;
|
||||
use pezframe_system::RawOrigin;
|
||||
use pezsp_core::H256;
|
||||
|
||||
/// Helper function to create a funded account
|
||||
fn funded_account<T: Config>(name: &'static str, index: u32) -> T::AccountId {
|
||||
let caller: T::AccountId = account(name, index, 0);
|
||||
let amount = T::KycApplicationDeposit::get() * 10u32.into();
|
||||
T::Currency::make_free_balance_be(&caller, amount);
|
||||
caller
|
||||
}
|
||||
|
||||
/// Helper function to setup a citizen (for referrer)
|
||||
fn setup_citizen<T: Config>(who: &T::AccountId) {
|
||||
KycStatuses::<T>::insert(who, KycLevel::Approved);
|
||||
}
|
||||
|
||||
/// Helper function to setup an applicant in PendingReferral state
|
||||
fn setup_pending_referral<T: Config>(applicant: &T::AccountId, referrer: &T::AccountId) {
|
||||
let identity_hash = H256::repeat_byte(0x01);
|
||||
let application = CitizenshipApplication { identity_hash, referrer: referrer.clone() };
|
||||
Applications::<T>::insert(applicant, application);
|
||||
KycStatuses::<T>::insert(applicant, KycLevel::PendingReferral);
|
||||
|
||||
// Reserve deposit
|
||||
let deposit = T::KycApplicationDeposit::get();
|
||||
let _ = T::Currency::reserve(applicant, deposit);
|
||||
}
|
||||
|
||||
/// Helper function to setup an applicant in ReferrerApproved state
|
||||
fn setup_referrer_approved<T: Config>(applicant: &T::AccountId, referrer: &T::AccountId) {
|
||||
let identity_hash = H256::repeat_byte(0x01);
|
||||
let application = CitizenshipApplication { identity_hash, referrer: referrer.clone() };
|
||||
Applications::<T>::insert(applicant, application);
|
||||
KycStatuses::<T>::insert(applicant, KycLevel::ReferrerApproved);
|
||||
|
||||
// Reserve deposit
|
||||
let deposit = T::KycApplicationDeposit::get();
|
||||
let _ = T::Currency::reserve(applicant, deposit);
|
||||
}
|
||||
|
||||
#[benchmarks]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn apply_for_citizenship() {
|
||||
let referrer: T::AccountId = funded_account::<T>("referrer", 0);
|
||||
setup_citizen::<T>(&referrer);
|
||||
|
||||
let applicant: T::AccountId = funded_account::<T>("applicant", 1);
|
||||
let identity_hash = H256::repeat_byte(0x42);
|
||||
|
||||
#[extrinsic_call]
|
||||
apply_for_citizenship(
|
||||
RawOrigin::Signed(applicant.clone()),
|
||||
identity_hash,
|
||||
referrer.clone(),
|
||||
);
|
||||
|
||||
assert_eq!(KycStatuses::<T>::get(&applicant), KycLevel::PendingReferral);
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn approve_referral() {
|
||||
let referrer: T::AccountId = funded_account::<T>("referrer", 0);
|
||||
setup_citizen::<T>(&referrer);
|
||||
|
||||
let applicant: T::AccountId = funded_account::<T>("applicant", 1);
|
||||
setup_pending_referral::<T>(&applicant, &referrer);
|
||||
|
||||
#[extrinsic_call]
|
||||
approve_referral(RawOrigin::Signed(referrer.clone()), applicant.clone());
|
||||
|
||||
assert_eq!(KycStatuses::<T>::get(&applicant), KycLevel::ReferrerApproved);
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn confirm_citizenship() {
|
||||
let referrer: T::AccountId = funded_account::<T>("referrer", 0);
|
||||
setup_citizen::<T>(&referrer);
|
||||
|
||||
let applicant: T::AccountId = funded_account::<T>("applicant", 1);
|
||||
setup_referrer_approved::<T>(&applicant, &referrer);
|
||||
|
||||
#[extrinsic_call]
|
||||
confirm_citizenship(RawOrigin::Signed(applicant.clone()));
|
||||
|
||||
assert_eq!(KycStatuses::<T>::get(&applicant), KycLevel::Approved);
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn revoke_citizenship() {
|
||||
let citizen: T::AccountId = funded_account::<T>("citizen", 0);
|
||||
setup_citizen::<T>(&citizen);
|
||||
|
||||
#[extrinsic_call]
|
||||
revoke_citizenship(RawOrigin::Root, citizen.clone());
|
||||
|
||||
assert_eq!(KycStatuses::<T>::get(&citizen), KycLevel::Revoked);
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn renounce_citizenship() {
|
||||
let citizen: T::AccountId = funded_account::<T>("citizen", 0);
|
||||
setup_citizen::<T>(&citizen);
|
||||
|
||||
#[extrinsic_call]
|
||||
renounce_citizenship(RawOrigin::Signed(citizen.clone()));
|
||||
|
||||
assert_eq!(KycStatuses::<T>::get(&citizen), KycLevel::NotStarted);
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn cancel_application() {
|
||||
let referrer: T::AccountId = funded_account::<T>("referrer", 0);
|
||||
setup_citizen::<T>(&referrer);
|
||||
|
||||
let applicant: T::AccountId = funded_account::<T>("applicant", 1);
|
||||
setup_pending_referral::<T>(&applicant, &referrer);
|
||||
|
||||
#[extrinsic_call]
|
||||
cancel_application(RawOrigin::Signed(applicant.clone()));
|
||||
|
||||
assert_eq!(KycStatuses::<T>::get(&applicant), KycLevel::NotStarted);
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(IdentityKyc, crate::mock::new_test_ext(), crate::mock::Test);
|
||||
}
|
||||
@@ -0,0 +1,563 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
//! # Identity & KYC Pallet - TRUSTLESS MODEL
|
||||
//!
|
||||
//! A privacy-preserving, decentralized citizenship verification system.
|
||||
//!
|
||||
//! ## Overview
|
||||
//!
|
||||
//! This pallet implements a **TRUSTLESS** citizenship verification where:
|
||||
//! - NO personal data is stored on-chain (only hash)
|
||||
//! - NO central authority/bot approves applications
|
||||
//! - Existing citizens vouch for new applicants (referral-based)
|
||||
//! - Direct responsibility: Referrers are accountable for their referrals
|
||||
//!
|
||||
//! ## Security Design (Kurdish People Safety)
|
||||
//!
|
||||
//! This system is designed to protect vulnerable populations (like Kurdish people)
|
||||
//! from hostile regimes that might try to identify applicants:
|
||||
//! - Only H256 hash of identity stored on-chain
|
||||
//! - Actual documents stored off-chain (IPFS/encrypted)
|
||||
//! - No admin can see or leak personal data
|
||||
//! - Referral chain creates accountability without central authority
|
||||
//!
|
||||
//! ## Citizenship Workflow
|
||||
//!
|
||||
//! ### 1. Application Phase
|
||||
//! - User creates identity hash off-chain: `H256(name + email + documents)`
|
||||
//! - User calls `apply_for_citizenship(identity_hash, referrer_account)`
|
||||
//! - Referrer MUST be an existing citizen (KycLevel::Approved)
|
||||
//! - Status changes to `PendingReferral`
|
||||
//!
|
||||
//! ### 2. Referrer Approval Phase
|
||||
//! - Referrer reviews applicant (off-chain verification)
|
||||
//! - Referrer calls `approve_referral(applicant)` to vouch for them
|
||||
//! - Status changes to `ReferrerApproved`
|
||||
//! - Referrer takes personal responsibility for this referral
|
||||
//!
|
||||
//! ### 3. Self-Confirmation Phase (Welati NFT Only)
|
||||
//! - Applicant calls `confirm_citizenship()` to complete the process
|
||||
//! - Status changes to `Approved`
|
||||
//! - Citizen NFT (Welati) is minted via self-confirmation
|
||||
//! - Referral hooks are triggered
|
||||
//!
|
||||
//! ## KYC Levels
|
||||
//!
|
||||
//! - **NotStarted** - No application submitted
|
||||
//! - **PendingReferral** - Waiting for referrer approval
|
||||
//! - **ReferrerApproved** - Referrer approved, ready for self-confirmation
|
||||
//! - **Approved** - Full citizen with all rights
|
||||
//! - **Revoked** - Citizenship revoked (governance decision)
|
||||
//!
|
||||
//! ## Privacy Features
|
||||
//!
|
||||
//! - **Hash-only storage**: No personal data on-chain
|
||||
//! - **Off-chain documents**: IPFS or encrypted storage
|
||||
//! - **No admin access**: Decentralized verification
|
||||
//! - **Referral accountability**: Social trust, not central authority
|
||||
//!
|
||||
//! ## Direct Responsibility Model
|
||||
//!
|
||||
//! When a citizen is found to be malicious:
|
||||
//! - ONLY their direct referrer is penalized
|
||||
//! - Penalty: Trust score reduction + potential citizenship review
|
||||
//! - Chain reactions are limited to direct relationships
|
||||
//! - Good referrals from bad actors are NOT penalized
|
||||
//!
|
||||
//! ## Interface
|
||||
//!
|
||||
//! ### User Extrinsics
|
||||
//!
|
||||
//! - `apply_for_citizenship(identity_hash, referrer)` - Submit citizenship application
|
||||
//! - `confirm_citizenship()` - Self-confirm after referrer approval (Welati only)
|
||||
//! - `renounce_citizenship()` - Voluntarily give up citizenship
|
||||
//!
|
||||
//! ### Referrer Extrinsics
|
||||
//!
|
||||
//! - `approve_referral(applicant)` - Vouch for an applicant
|
||||
//!
|
||||
//! ### Governance Extrinsics (Root only)
|
||||
//!
|
||||
//! - `revoke_citizenship(who)` - Revoke citizenship (governance decision)
|
||||
//!
|
||||
//! ## Runtime Integration Example
|
||||
//!
|
||||
//! ```ignore
|
||||
//! impl pezpallet_identity_kyc::Config for Runtime {
|
||||
//! type RuntimeEvent = RuntimeEvent;
|
||||
//! type Currency = Balances;
|
||||
//! type WeightInfo = pezpallet_identity_kyc::weights::BizinikiwiWeight<Runtime>;
|
||||
//! type OnKycApproved = Referral;
|
||||
//! type CitizenNftProvider = Tiki;
|
||||
//! type KycApplicationDeposit = ConstU128<1_000_000_000_000>; // Spam prevention
|
||||
//! type MaxStringLength = ConstU32<128>;
|
||||
//! type MaxCidLength = ConstU32<64>;
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
pub use pallet::*;
|
||||
pub mod types;
|
||||
use types::*;
|
||||
pub mod weights;
|
||||
pub use weights::WeightInfo;
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
mod benchmarking;
|
||||
|
||||
extern crate alloc;
|
||||
use pezframe_support::{pezpallet_prelude::*, traits::ReservableCurrency};
|
||||
use pezframe_system::pezpallet_prelude::*;
|
||||
use pezsp_core::H256;
|
||||
|
||||
#[pezframe_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: pezframe_system::Config {
|
||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
|
||||
type Currency: ReservableCurrency<Self::AccountId>;
|
||||
|
||||
/// Origin that can revoke citizenship (governance/root)
|
||||
type GovernanceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
|
||||
|
||||
type WeightInfo: WeightInfo;
|
||||
|
||||
/// Hook called when citizenship is approved - used by referral pallet
|
||||
type OnKycApproved: crate::types::OnKycApproved<Self::AccountId>;
|
||||
|
||||
/// Hook called when citizenship is revoked - used by referral pallet for penalty
|
||||
type OnCitizenshipRevoked: crate::types::OnCitizenshipRevoked<Self::AccountId>;
|
||||
|
||||
/// Provider for minting citizen NFTs - used by tiki pallet
|
||||
type CitizenNftProvider: crate::types::CitizenNftProvider<Self::AccountId>;
|
||||
|
||||
/// Deposit required to apply (spam prevention, returned on approval)
|
||||
#[pallet::constant]
|
||||
type KycApplicationDeposit: Get<BalanceOf<Self>>;
|
||||
|
||||
/// Max string length for legacy storage
|
||||
#[pallet::constant]
|
||||
type MaxStringLength: Get<u32>;
|
||||
|
||||
/// Max CID length for legacy storage
|
||||
#[pallet::constant]
|
||||
type MaxCidLength: Get<u32>;
|
||||
}
|
||||
|
||||
pub type BalanceOf<T> = <<T as Config>::Currency as pezframe_support::traits::Currency<
|
||||
<T as pezframe_system::Config>::AccountId,
|
||||
>>::Balance;
|
||||
|
||||
// ============= STORAGE =============
|
||||
|
||||
/// Citizenship applications (applicant -> application)
|
||||
/// PRIVACY: Only hash stored, no personal data
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn applications)]
|
||||
pub type Applications<T: Config> =
|
||||
StorageMap<_, Blake2_128Concat, T::AccountId, CitizenshipApplication<T::AccountId>>;
|
||||
|
||||
/// Current citizenship status per account
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn kyc_status_of)]
|
||||
pub type KycStatuses<T: Config> =
|
||||
StorageMap<_, Blake2_128Concat, T::AccountId, KycLevel, ValueQuery>;
|
||||
|
||||
/// Identity hashes of approved citizens (for verification)
|
||||
/// Can be used to prove citizenship without revealing identity
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn identity_hash_of)]
|
||||
pub type IdentityHashes<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, H256>;
|
||||
|
||||
/// Referrer of approved citizens (for direct responsibility tracking)
|
||||
/// Kept permanently for penalty system even after application is removed
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn citizen_referrer)]
|
||||
pub type CitizenReferrers<T: Config> =
|
||||
StorageMap<_, Blake2_128Concat, T::AccountId, T::AccountId>;
|
||||
|
||||
// ============= LEGACY STORAGE (for migration) =============
|
||||
|
||||
/// Legacy: Identity info storage (deprecated, kept for migration)
|
||||
#[pallet::storage]
|
||||
pub type Identities<T: Config> =
|
||||
StorageMap<_, Blake2_128Concat, T::AccountId, IdentityInfo<T::MaxStringLength>>;
|
||||
|
||||
/// Legacy: Pending KYC applications (deprecated, kept for migration)
|
||||
#[pallet::storage]
|
||||
pub type PendingKycApplications<T: Config> = StorageMap<
|
||||
_,
|
||||
Blake2_128Concat,
|
||||
T::AccountId,
|
||||
KycApplication<T::MaxStringLength, T::MaxCidLength>,
|
||||
>;
|
||||
|
||||
// ============= GENESIS CONFIG =============
|
||||
|
||||
/// Genesis configuration for bootstrapping initial citizens
|
||||
/// BOOTSTRAP: Solves chicken-egg problem - first citizens need to exist for others to join
|
||||
#[pallet::genesis_config]
|
||||
#[derive(pezframe_support::DefaultNoBound)]
|
||||
pub struct GenesisConfig<T: Config> {
|
||||
/// List of founding citizens (AccountId, IdentityHash)
|
||||
/// These accounts start with Approved status and can accept referrals immediately
|
||||
pub founding_citizens: alloc::vec::Vec<(T::AccountId, H256)>,
|
||||
#[serde(skip)]
|
||||
pub _phantom: core::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
#[pallet::genesis_build]
|
||||
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
||||
fn build(&self) {
|
||||
// Initialize founding citizens with Approved status
|
||||
for (account, identity_hash) in &self.founding_citizens {
|
||||
// Set status to Approved (citizen)
|
||||
KycStatuses::<T>::insert(account, KycLevel::Approved);
|
||||
// Store identity hash
|
||||
IdentityHashes::<T>::insert(account, *identity_hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============= EVENTS =============
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
/// New citizenship application submitted
|
||||
CitizenshipApplied { applicant: T::AccountId, referrer: T::AccountId, identity_hash: H256 },
|
||||
/// Referrer approved the application
|
||||
ReferralApproved { referrer: T::AccountId, applicant: T::AccountId },
|
||||
/// Applicant self-confirmed their citizenship (Welati NFT minted)
|
||||
CitizenshipConfirmed { who: T::AccountId },
|
||||
/// Citizenship was revoked (by governance)
|
||||
CitizenshipRevoked { who: T::AccountId },
|
||||
/// User renounced their citizenship
|
||||
CitizenshipRenounced { who: T::AccountId },
|
||||
/// Application was cancelled by the applicant
|
||||
ApplicationCancelled { who: T::AccountId },
|
||||
}
|
||||
|
||||
// ============= ERRORS =============
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
/// Application already exists for this account
|
||||
ApplicationAlreadyExists,
|
||||
/// No application found for this account
|
||||
ApplicationNotFound,
|
||||
/// Referrer is not a citizen (must have Approved status)
|
||||
ReferrerNotCitizen,
|
||||
/// Cannot refer yourself
|
||||
SelfReferral,
|
||||
/// Cannot approve referral in current state (must be PendingReferral)
|
||||
CannotApproveInCurrentState,
|
||||
/// Cannot confirm in current state (must be ReferrerApproved)
|
||||
CannotConfirmInCurrentState,
|
||||
/// Cannot revoke in current state (must be Approved)
|
||||
CannotRevokeInCurrentState,
|
||||
/// User is not a citizen (cannot renounce)
|
||||
NotACitizen,
|
||||
/// Only the referrer can approve this application
|
||||
NotTheReferrer,
|
||||
/// Cannot cancel application in current state (must be PendingReferral)
|
||||
CannotCancelInCurrentState,
|
||||
}
|
||||
|
||||
// ============= EXTRINSICS =============
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Apply for citizenship with identity hash and referrer
|
||||
///
|
||||
/// TRUSTLESS: No admin involved, referrer vouches for applicant
|
||||
/// PRIVACY: Only hash stored, actual identity is off-chain
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `identity_hash`: H256 hash of identity documents (calculated off-chain)
|
||||
/// - `referrer`: Account of existing citizen who will vouch for you
|
||||
///
|
||||
/// # Workflow
|
||||
/// 1. Applicant submits hash + referrer
|
||||
/// 2. Deposit is reserved (spam prevention)
|
||||
/// 3. Status becomes PendingReferral
|
||||
/// 4. Referrer must call approve_referral
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight(T::WeightInfo::apply_for_citizenship())]
|
||||
pub fn apply_for_citizenship(
|
||||
origin: OriginFor<T>,
|
||||
identity_hash: H256,
|
||||
referrer: T::AccountId,
|
||||
) -> DispatchResult {
|
||||
let applicant = ensure_signed(origin)?;
|
||||
|
||||
// Cannot refer yourself
|
||||
ensure!(applicant != referrer, Error::<T>::SelfReferral);
|
||||
|
||||
// Must not have existing application
|
||||
ensure!(
|
||||
KycStatuses::<T>::get(&applicant) == KycLevel::NotStarted,
|
||||
Error::<T>::ApplicationAlreadyExists
|
||||
);
|
||||
|
||||
// Referrer must be an approved citizen
|
||||
ensure!(
|
||||
KycStatuses::<T>::get(&referrer) == KycLevel::Approved,
|
||||
Error::<T>::ReferrerNotCitizen
|
||||
);
|
||||
|
||||
// Reserve deposit (spam prevention, returned on approval)
|
||||
let deposit = T::KycApplicationDeposit::get();
|
||||
T::Currency::reserve(&applicant, deposit)?;
|
||||
|
||||
// Store application (only hash, no personal data)
|
||||
let application = CitizenshipApplication { identity_hash, referrer: referrer.clone() };
|
||||
Applications::<T>::insert(&applicant, application);
|
||||
|
||||
// Update status
|
||||
KycStatuses::<T>::insert(&applicant, KycLevel::PendingReferral);
|
||||
|
||||
Self::deposit_event(Event::CitizenshipApplied { applicant, referrer, identity_hash });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Referrer approves an applicant's citizenship application
|
||||
///
|
||||
/// TRUSTLESS: Referrer takes personal responsibility for this referral
|
||||
/// ACCOUNTABILITY: If applicant turns out malicious, referrer is penalized
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `applicant`: Account of the person you're vouching for
|
||||
///
|
||||
/// # Requirements
|
||||
/// - Caller must be the referrer specified in the application
|
||||
/// - Application must be in PendingReferral state
|
||||
#[pallet::call_index(1)]
|
||||
#[pallet::weight(T::WeightInfo::approve_referral())]
|
||||
pub fn approve_referral(origin: OriginFor<T>, applicant: T::AccountId) -> DispatchResult {
|
||||
let caller = ensure_signed(origin)?;
|
||||
|
||||
// Must be in PendingReferral state
|
||||
ensure!(
|
||||
KycStatuses::<T>::get(&applicant) == KycLevel::PendingReferral,
|
||||
Error::<T>::CannotApproveInCurrentState
|
||||
);
|
||||
|
||||
// Get application
|
||||
let application =
|
||||
Applications::<T>::get(&applicant).ok_or(Error::<T>::ApplicationNotFound)?;
|
||||
|
||||
// Only the referrer can approve
|
||||
ensure!(application.referrer == caller, Error::<T>::NotTheReferrer);
|
||||
|
||||
// Update status to ReferrerApproved
|
||||
KycStatuses::<T>::insert(&applicant, KycLevel::ReferrerApproved);
|
||||
|
||||
Self::deposit_event(Event::ReferralApproved { referrer: caller, applicant });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Self-confirm citizenship after referrer approval
|
||||
///
|
||||
/// TRUSTLESS: Applicant confirms themselves, no admin needed
|
||||
/// WELATI ONLY: This mints the Citizen NFT via self-confirmation
|
||||
///
|
||||
/// # Workflow
|
||||
/// 1. Deposit is returned
|
||||
/// 2. Identity hash is stored permanently
|
||||
/// 3. Status becomes Approved
|
||||
/// 4. Citizen NFT (Welati) is minted
|
||||
/// 5. Referral hooks are triggered
|
||||
#[pallet::call_index(2)]
|
||||
#[pallet::weight(T::WeightInfo::confirm_citizenship())]
|
||||
pub fn confirm_citizenship(origin: OriginFor<T>) -> DispatchResult {
|
||||
let applicant = ensure_signed(origin)?;
|
||||
|
||||
// Must be in ReferrerApproved state
|
||||
ensure!(
|
||||
KycStatuses::<T>::get(&applicant) == KycLevel::ReferrerApproved,
|
||||
Error::<T>::CannotConfirmInCurrentState
|
||||
);
|
||||
|
||||
// Get application
|
||||
let application =
|
||||
Applications::<T>::take(&applicant).ok_or(Error::<T>::ApplicationNotFound)?;
|
||||
|
||||
// Return deposit
|
||||
let deposit = T::KycApplicationDeposit::get();
|
||||
T::Currency::unreserve(&applicant, deposit);
|
||||
|
||||
// Store identity hash permanently (for proof of citizenship)
|
||||
IdentityHashes::<T>::insert(&applicant, application.identity_hash);
|
||||
|
||||
// Store referrer permanently (for direct responsibility tracking)
|
||||
// This is needed even after Applications is removed for penalty system
|
||||
CitizenReferrers::<T>::insert(&applicant, application.referrer.clone());
|
||||
|
||||
// Update status to Approved
|
||||
KycStatuses::<T>::insert(&applicant, KycLevel::Approved);
|
||||
|
||||
// Mint citizen NFT with self-confirmation (Welati tiki)
|
||||
if let Err(e) = T::CitizenNftProvider::mint_citizen_nft_confirmed(&applicant) {
|
||||
log::warn!("Failed to mint citizen NFT for {:?}: {:?}", applicant, e);
|
||||
// Don't fail - user is still a citizen
|
||||
}
|
||||
|
||||
// Trigger referral hooks (for referral pallet)
|
||||
// Pass referrer parameter to avoid data loss between pallets
|
||||
T::OnKycApproved::on_kyc_approved(&applicant, &application.referrer);
|
||||
|
||||
Self::deposit_event(Event::CitizenshipConfirmed { who: applicant });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Revoke citizenship (governance only)
|
||||
///
|
||||
/// Used for malicious actors identified by governance
|
||||
/// DIRECT RESPONSIBILITY: Triggers penalty for the referrer via referral pallet
|
||||
#[pallet::call_index(3)]
|
||||
#[pallet::weight(T::WeightInfo::revoke_citizenship())]
|
||||
pub fn revoke_citizenship(origin: OriginFor<T>, who: T::AccountId) -> DispatchResult {
|
||||
T::GovernanceOrigin::ensure_origin(origin)?;
|
||||
|
||||
ensure!(
|
||||
KycStatuses::<T>::get(&who) == KycLevel::Approved,
|
||||
Error::<T>::CannotRevokeInCurrentState
|
||||
);
|
||||
|
||||
// Update status
|
||||
KycStatuses::<T>::insert(&who, KycLevel::Revoked);
|
||||
|
||||
// Burn citizen NFT
|
||||
if let Err(e) = T::CitizenNftProvider::burn_citizen_nft(&who) {
|
||||
log::warn!("Failed to burn citizen NFT for {:?}: {:?}", who, e);
|
||||
}
|
||||
|
||||
// Trigger direct responsibility penalty for the referrer
|
||||
// This hook notifies the referral pallet to penalize the referrer
|
||||
T::OnCitizenshipRevoked::on_citizenship_revoked(&who);
|
||||
|
||||
Self::deposit_event(Event::CitizenshipRevoked { who });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Renounce citizenship (voluntary exit)
|
||||
///
|
||||
/// Users can freely leave the system
|
||||
#[pallet::call_index(4)]
|
||||
#[pallet::weight(T::WeightInfo::renounce_citizenship())]
|
||||
pub fn renounce_citizenship(origin: OriginFor<T>) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
|
||||
ensure!(KycStatuses::<T>::get(&who) == KycLevel::Approved, Error::<T>::NotACitizen);
|
||||
|
||||
// Burn citizen NFT
|
||||
T::CitizenNftProvider::burn_citizen_nft(&who)?;
|
||||
|
||||
// Reset status
|
||||
KycStatuses::<T>::insert(&who, KycLevel::NotStarted);
|
||||
|
||||
// Remove identity hash
|
||||
IdentityHashes::<T>::remove(&who);
|
||||
|
||||
Self::deposit_event(Event::CitizenshipRenounced { who });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Cancel pending application and retrieve deposit
|
||||
///
|
||||
/// Useful if referrer is unresponsive or user made a mistake.
|
||||
/// SAFETY: Only works in PendingReferral state (not yet approved)
|
||||
#[pallet::call_index(5)]
|
||||
#[pallet::weight(T::WeightInfo::cancel_application())]
|
||||
pub fn cancel_application(origin: OriginFor<T>) -> DispatchResult {
|
||||
let applicant = ensure_signed(origin)?;
|
||||
|
||||
// Must be in PendingReferral state (not yet approved by referrer)
|
||||
ensure!(
|
||||
KycStatuses::<T>::get(&applicant) == KycLevel::PendingReferral,
|
||||
Error::<T>::CannotCancelInCurrentState
|
||||
);
|
||||
|
||||
// Remove application
|
||||
Applications::<T>::remove(&applicant);
|
||||
|
||||
// Reset status
|
||||
KycStatuses::<T>::insert(&applicant, KycLevel::NotStarted);
|
||||
|
||||
// Unreserve deposit
|
||||
let deposit = T::KycApplicationDeposit::get();
|
||||
T::Currency::unreserve(&applicant, deposit);
|
||||
|
||||
Self::deposit_event(Event::ApplicationCancelled { who: applicant });
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============= TRAIT IMPLEMENTATIONS =============
|
||||
|
||||
pub use types::KycStatus;
|
||||
|
||||
impl<T: Config> types::KycStatus<T::AccountId> for Pallet<T> {
|
||||
fn get_kyc_status(who: &T::AccountId) -> KycLevel {
|
||||
KycStatuses::<T>::get(who)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> IdentityInfoProvider<T::AccountId, T::MaxStringLength> for Pallet<T> {
|
||||
fn get_identity_info(who: &T::AccountId) -> Option<IdentityInfo<T::MaxStringLength>> {
|
||||
// Legacy: Return from old storage if exists
|
||||
Identities::<T>::get(who)
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper methods for checking citizenship
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Check if account is a citizen
|
||||
pub fn is_citizen(who: &T::AccountId) -> bool {
|
||||
KycStatuses::<T>::get(who) == KycLevel::Approved
|
||||
}
|
||||
|
||||
/// Count total number of citizens
|
||||
pub fn citizen_count() -> u32 {
|
||||
KycStatuses::<T>::iter()
|
||||
.filter(|(_, status)| *status == KycLevel::Approved)
|
||||
.count() as u32
|
||||
}
|
||||
|
||||
/// Get the referrer of a citizen or applicant
|
||||
/// Checks both pending applications and approved citizen records
|
||||
pub fn get_referrer(who: &T::AccountId) -> Option<T::AccountId> {
|
||||
// First check permanent storage (for approved citizens)
|
||||
CitizenReferrers::<T>::get(who)
|
||||
// Then check pending applications
|
||||
.or_else(|| Applications::<T>::get(who).map(|app| app.referrer))
|
||||
}
|
||||
|
||||
/// Get identity hash of a citizen
|
||||
pub fn get_identity_hash(who: &T::AccountId) -> Option<H256> {
|
||||
IdentityHashes::<T>::get(who)
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for trust pallet integration
|
||||
pub trait CitizenshipStatusProvider<AccountId> {
|
||||
fn is_citizen(who: &AccountId) -> bool;
|
||||
}
|
||||
|
||||
impl<T: Config> CitizenshipStatusProvider<T::AccountId> for Pallet<T> {
|
||||
fn is_citizen(who: &T::AccountId) -> bool {
|
||||
KycStatuses::<T>::get(who) == KycLevel::Approved
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
use crate as pezpallet_identity_kyc;
|
||||
use pezframe_support::{
|
||||
construct_runtime, derive_impl, parameter_types,
|
||||
traits::{ConstU128, ConstU32},
|
||||
};
|
||||
use pezframe_system::EnsureRoot;
|
||||
use pezsp_core::H256;
|
||||
use pezsp_runtime::BuildStorage;
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<Test>;
|
||||
pub type AccountId = u64;
|
||||
pub type Balance = u128;
|
||||
|
||||
// Founding citizen for genesis tests
|
||||
pub const FOUNDER: AccountId = 100;
|
||||
pub const CITIZEN_1: AccountId = 1;
|
||||
pub const CITIZEN_2: AccountId = 2;
|
||||
pub const APPLICANT: AccountId = 3;
|
||||
|
||||
construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: pezframe_system,
|
||||
Balances: pezpallet_balances,
|
||||
IdentityKyc: pezpallet_identity_kyc,
|
||||
}
|
||||
);
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Test {
|
||||
type Block = Block;
|
||||
type AccountData = pezpallet_balances::AccountData<Balance>;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for Test {
|
||||
type Balance = Balance;
|
||||
type ExistentialDeposit = ConstU128<1>;
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const KycApplicationDepositAmount: Balance = 100;
|
||||
pub const MaxStringLen: u32 = 50;
|
||||
pub const MaxCidLen: u32 = 128;
|
||||
}
|
||||
|
||||
// Mock implementation for OnKycApproved hook
|
||||
// UPDATED: Now includes referrer parameter
|
||||
pub struct MockOnKycApproved;
|
||||
impl crate::types::OnKycApproved<AccountId> for MockOnKycApproved {
|
||||
fn on_kyc_approved(_who: &AccountId, _referrer: &AccountId) {
|
||||
// No-op for tests - in real runtime this triggers referral pallet
|
||||
}
|
||||
}
|
||||
|
||||
// Mock implementation for OnCitizenshipRevoked hook
|
||||
pub struct MockOnCitizenshipRevoked;
|
||||
impl crate::types::OnCitizenshipRevoked<AccountId> for MockOnCitizenshipRevoked {
|
||||
fn on_citizenship_revoked(_who: &AccountId) {
|
||||
// No-op for tests - in real runtime this triggers penalty system
|
||||
}
|
||||
}
|
||||
|
||||
// Mock implementation for CitizenNftProvider
|
||||
pub struct MockCitizenNftProvider;
|
||||
impl crate::types::CitizenNftProvider<AccountId> for MockCitizenNftProvider {
|
||||
fn mint_citizen_nft(_who: &AccountId) -> pezsp_runtime::DispatchResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mint_citizen_nft_confirmed(_who: &AccountId) -> pezsp_runtime::DispatchResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn burn_citizen_nft(_who: &AccountId) -> pezsp_runtime::DispatchResult {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Currency = Balances;
|
||||
type GovernanceOrigin = EnsureRoot<Self::AccountId>;
|
||||
type WeightInfo = ();
|
||||
type OnKycApproved = MockOnKycApproved;
|
||||
type OnCitizenshipRevoked = MockOnCitizenshipRevoked;
|
||||
type CitizenNftProvider = MockCitizenNftProvider;
|
||||
type KycApplicationDeposit = KycApplicationDepositAmount;
|
||||
type MaxStringLength = MaxStringLen;
|
||||
type MaxCidLength = MaxCidLen;
|
||||
}
|
||||
|
||||
/// Build test externalities with founding citizens
|
||||
pub fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
let mut t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
|
||||
pezpallet_balances::GenesisConfig::<Test> {
|
||||
balances: vec![
|
||||
(FOUNDER, 1_000_000),
|
||||
(CITIZEN_1, 10_000),
|
||||
(CITIZEN_2, 10_000),
|
||||
(APPLICANT, 10_000),
|
||||
],
|
||||
..Default::default()
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
// Add founding citizen via genesis config
|
||||
pezpallet_identity_kyc::GenesisConfig::<Test> {
|
||||
founding_citizens: vec![
|
||||
(FOUNDER, H256::from_low_u64_be(1)), // Founder is pre-approved
|
||||
(CITIZEN_1, H256::from_low_u64_be(2)), // Citizen 1 is pre-approved
|
||||
],
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = pezsp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
ext
|
||||
}
|
||||
|
||||
/// Build test externalities without founding citizens (for edge case tests)
|
||||
pub fn new_test_ext_empty() -> pezsp_io::TestExternalities {
|
||||
let mut t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
|
||||
pezpallet_balances::GenesisConfig::<Test> {
|
||||
balances: vec![(FOUNDER, 1_000_000), (CITIZEN_1, 10_000), (APPLICANT, 10_000)],
|
||||
..Default::default()
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = pezsp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
ext
|
||||
}
|
||||
@@ -0,0 +1,551 @@
|
||||
use crate::{mock::*, types::KycLevel, Error, Event};
|
||||
use pezframe_support::{assert_noop, assert_ok, traits::Currency};
|
||||
use pezsp_core::H256;
|
||||
use pezsp_runtime::DispatchError;
|
||||
|
||||
// Kolay erişim için paletimize bir takma ad veriyoruz.
|
||||
type IdentityKycPallet = crate::Pallet<Test>;
|
||||
|
||||
// ============================================================================
|
||||
// Genesis Config Tests
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn genesis_config_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// FOUNDER and CITIZEN_1 should be pre-approved via genesis
|
||||
assert_eq!(IdentityKycPallet::kyc_status_of(FOUNDER), KycLevel::Approved);
|
||||
assert_eq!(IdentityKycPallet::kyc_status_of(CITIZEN_1), KycLevel::Approved);
|
||||
|
||||
// Their identity hashes should be stored
|
||||
assert!(IdentityKycPallet::identity_hash_of(FOUNDER).is_some());
|
||||
assert!(IdentityKycPallet::identity_hash_of(CITIZEN_1).is_some());
|
||||
|
||||
// Non-founding users should be NotStarted
|
||||
assert_eq!(IdentityKycPallet::kyc_status_of(APPLICANT), KycLevel::NotStarted);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// apply_for_citizenship Tests
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn apply_for_citizenship_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let identity_hash = H256::from_low_u64_be(12345);
|
||||
|
||||
// APPLICANT applies with CITIZEN_1 as referrer (who is pre-approved)
|
||||
assert_ok!(IdentityKycPallet::apply_for_citizenship(
|
||||
RuntimeOrigin::signed(APPLICANT),
|
||||
identity_hash,
|
||||
CITIZEN_1
|
||||
));
|
||||
|
||||
// Check status changed to PendingReferral
|
||||
assert_eq!(IdentityKycPallet::kyc_status_of(APPLICANT), KycLevel::PendingReferral);
|
||||
|
||||
// Check application was stored
|
||||
let app = IdentityKycPallet::applications(APPLICANT).expect("Application should exist");
|
||||
assert_eq!(app.identity_hash, identity_hash);
|
||||
assert_eq!(app.referrer, CITIZEN_1);
|
||||
|
||||
// Check deposit was reserved
|
||||
assert_eq!(Balances::reserved_balance(APPLICANT), KycApplicationDepositAmount::get());
|
||||
|
||||
// Check event was emitted
|
||||
System::assert_last_event(
|
||||
Event::CitizenshipApplied { applicant: APPLICANT, referrer: CITIZEN_1, identity_hash }
|
||||
.into(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_for_citizenship_fails_if_self_referral() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Cannot refer yourself
|
||||
assert_noop!(
|
||||
IdentityKycPallet::apply_for_citizenship(
|
||||
RuntimeOrigin::signed(CITIZEN_1),
|
||||
H256::from_low_u64_be(999),
|
||||
CITIZEN_1 // Same as caller
|
||||
),
|
||||
Error::<Test>::SelfReferral
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_for_citizenship_fails_if_referrer_not_citizen() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// APPLICANT is not a citizen, so cannot be a referrer
|
||||
assert_noop!(
|
||||
IdentityKycPallet::apply_for_citizenship(
|
||||
RuntimeOrigin::signed(CITIZEN_2),
|
||||
H256::from_low_u64_be(999),
|
||||
APPLICANT // Not a citizen
|
||||
),
|
||||
Error::<Test>::ReferrerNotCitizen
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_for_citizenship_fails_if_already_applied() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let identity_hash = H256::from_low_u64_be(12345);
|
||||
|
||||
// First application succeeds
|
||||
assert_ok!(IdentityKycPallet::apply_for_citizenship(
|
||||
RuntimeOrigin::signed(APPLICANT),
|
||||
identity_hash,
|
||||
CITIZEN_1
|
||||
));
|
||||
|
||||
// Second application fails
|
||||
assert_noop!(
|
||||
IdentityKycPallet::apply_for_citizenship(
|
||||
RuntimeOrigin::signed(APPLICANT),
|
||||
H256::from_low_u64_be(99999),
|
||||
CITIZEN_1
|
||||
),
|
||||
Error::<Test>::ApplicationAlreadyExists
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_for_citizenship_fails_insufficient_balance() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let poor_user = 999; // No balance in genesis
|
||||
|
||||
assert_noop!(
|
||||
IdentityKycPallet::apply_for_citizenship(
|
||||
RuntimeOrigin::signed(poor_user),
|
||||
H256::from_low_u64_be(12345),
|
||||
CITIZEN_1
|
||||
),
|
||||
pezpallet_balances::Error::<Test>::InsufficientBalance
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// approve_referral Tests
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn approve_referral_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let identity_hash = H256::from_low_u64_be(12345);
|
||||
|
||||
// APPLICANT applies with CITIZEN_1 as referrer
|
||||
assert_ok!(IdentityKycPallet::apply_for_citizenship(
|
||||
RuntimeOrigin::signed(APPLICANT),
|
||||
identity_hash,
|
||||
CITIZEN_1
|
||||
));
|
||||
|
||||
// CITIZEN_1 approves the referral
|
||||
assert_ok!(IdentityKycPallet::approve_referral(
|
||||
RuntimeOrigin::signed(CITIZEN_1),
|
||||
APPLICANT
|
||||
));
|
||||
|
||||
// Check status changed to ReferrerApproved
|
||||
assert_eq!(IdentityKycPallet::kyc_status_of(APPLICANT), KycLevel::ReferrerApproved);
|
||||
|
||||
// Check event
|
||||
System::assert_last_event(
|
||||
Event::ReferralApproved { referrer: CITIZEN_1, applicant: APPLICANT }.into(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approve_referral_fails_if_not_referrer() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// APPLICANT applies with CITIZEN_1 as referrer
|
||||
assert_ok!(IdentityKycPallet::apply_for_citizenship(
|
||||
RuntimeOrigin::signed(APPLICANT),
|
||||
H256::from_low_u64_be(12345),
|
||||
CITIZEN_1
|
||||
));
|
||||
|
||||
// FOUNDER (different citizen) cannot approve
|
||||
assert_noop!(
|
||||
IdentityKycPallet::approve_referral(RuntimeOrigin::signed(FOUNDER), APPLICANT),
|
||||
Error::<Test>::NotTheReferrer
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approve_referral_fails_if_not_pending() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Try to approve referral for someone who hasn't applied
|
||||
assert_noop!(
|
||||
IdentityKycPallet::approve_referral(RuntimeOrigin::signed(CITIZEN_1), APPLICANT),
|
||||
Error::<Test>::CannotApproveInCurrentState
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// confirm_citizenship Tests (Self-confirmation for Welati NFT)
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn confirm_citizenship_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let identity_hash = H256::from_low_u64_be(12345);
|
||||
let initial_balance = Balances::free_balance(APPLICANT);
|
||||
|
||||
// Apply
|
||||
assert_ok!(IdentityKycPallet::apply_for_citizenship(
|
||||
RuntimeOrigin::signed(APPLICANT),
|
||||
identity_hash,
|
||||
CITIZEN_1
|
||||
));
|
||||
|
||||
// Referrer approves
|
||||
assert_ok!(IdentityKycPallet::approve_referral(
|
||||
RuntimeOrigin::signed(CITIZEN_1),
|
||||
APPLICANT
|
||||
));
|
||||
|
||||
// Self-confirm
|
||||
assert_ok!(IdentityKycPallet::confirm_citizenship(RuntimeOrigin::signed(APPLICANT)));
|
||||
|
||||
// Check status is Approved
|
||||
assert_eq!(IdentityKycPallet::kyc_status_of(APPLICANT), KycLevel::Approved);
|
||||
|
||||
// Check identity hash is stored permanently
|
||||
assert_eq!(IdentityKycPallet::identity_hash_of(APPLICANT), Some(identity_hash));
|
||||
|
||||
// Check referrer is stored permanently
|
||||
assert_eq!(IdentityKycPallet::citizen_referrer(APPLICANT), Some(CITIZEN_1));
|
||||
|
||||
// Check application was removed
|
||||
assert!(IdentityKycPallet::applications(APPLICANT).is_none());
|
||||
|
||||
// Check deposit was returned
|
||||
assert_eq!(Balances::reserved_balance(APPLICANT), 0);
|
||||
assert_eq!(Balances::free_balance(APPLICANT), initial_balance);
|
||||
|
||||
// Check event
|
||||
System::assert_last_event(Event::CitizenshipConfirmed { who: APPLICANT }.into());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirm_citizenship_fails_if_not_referrer_approved() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Apply but don't get referrer approval
|
||||
assert_ok!(IdentityKycPallet::apply_for_citizenship(
|
||||
RuntimeOrigin::signed(APPLICANT),
|
||||
H256::from_low_u64_be(12345),
|
||||
CITIZEN_1
|
||||
));
|
||||
|
||||
// Try to self-confirm without referrer approval
|
||||
assert_noop!(
|
||||
IdentityKycPallet::confirm_citizenship(RuntimeOrigin::signed(APPLICANT)),
|
||||
Error::<Test>::CannotConfirmInCurrentState
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirm_citizenship_fails_if_not_applied() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Try to confirm without applying
|
||||
assert_noop!(
|
||||
IdentityKycPallet::confirm_citizenship(RuntimeOrigin::signed(APPLICANT)),
|
||||
Error::<Test>::CannotConfirmInCurrentState
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// cancel_application Tests
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn cancel_application_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let initial_balance = Balances::free_balance(APPLICANT);
|
||||
|
||||
// Apply
|
||||
assert_ok!(IdentityKycPallet::apply_for_citizenship(
|
||||
RuntimeOrigin::signed(APPLICANT),
|
||||
H256::from_low_u64_be(12345),
|
||||
CITIZEN_1
|
||||
));
|
||||
|
||||
// Deposit should be reserved
|
||||
assert_eq!(Balances::reserved_balance(APPLICANT), KycApplicationDepositAmount::get());
|
||||
|
||||
// Cancel
|
||||
assert_ok!(IdentityKycPallet::cancel_application(RuntimeOrigin::signed(APPLICANT)));
|
||||
|
||||
// Status should be reset to NotStarted
|
||||
assert_eq!(IdentityKycPallet::kyc_status_of(APPLICANT), KycLevel::NotStarted);
|
||||
|
||||
// Application should be removed
|
||||
assert!(IdentityKycPallet::applications(APPLICANT).is_none());
|
||||
|
||||
// Deposit should be returned
|
||||
assert_eq!(Balances::reserved_balance(APPLICANT), 0);
|
||||
assert_eq!(Balances::free_balance(APPLICANT), initial_balance);
|
||||
|
||||
// Event
|
||||
System::assert_last_event(Event::ApplicationCancelled { who: APPLICANT }.into());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancel_application_fails_if_not_pending_referral() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Apply and get referrer approval
|
||||
assert_ok!(IdentityKycPallet::apply_for_citizenship(
|
||||
RuntimeOrigin::signed(APPLICANT),
|
||||
H256::from_low_u64_be(12345),
|
||||
CITIZEN_1
|
||||
));
|
||||
assert_ok!(IdentityKycPallet::approve_referral(
|
||||
RuntimeOrigin::signed(CITIZEN_1),
|
||||
APPLICANT
|
||||
));
|
||||
|
||||
// Cannot cancel after referrer approved (status is ReferrerApproved)
|
||||
assert_noop!(
|
||||
IdentityKycPallet::cancel_application(RuntimeOrigin::signed(APPLICANT)),
|
||||
Error::<Test>::CannotCancelInCurrentState
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancel_application_allows_reapplication() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// First application
|
||||
assert_ok!(IdentityKycPallet::apply_for_citizenship(
|
||||
RuntimeOrigin::signed(APPLICANT),
|
||||
H256::from_low_u64_be(12345),
|
||||
CITIZEN_1
|
||||
));
|
||||
|
||||
// Cancel
|
||||
assert_ok!(IdentityKycPallet::cancel_application(RuntimeOrigin::signed(APPLICANT)));
|
||||
|
||||
// Can apply again with different referrer
|
||||
assert_ok!(IdentityKycPallet::apply_for_citizenship(
|
||||
RuntimeOrigin::signed(APPLICANT),
|
||||
H256::from_low_u64_be(99999),
|
||||
FOUNDER // Different referrer this time
|
||||
));
|
||||
|
||||
assert_eq!(IdentityKycPallet::kyc_status_of(APPLICANT), KycLevel::PendingReferral);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// revoke_citizenship Tests (Governance action)
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn revoke_citizenship_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Complete citizenship flow for APPLICANT
|
||||
assert_ok!(IdentityKycPallet::apply_for_citizenship(
|
||||
RuntimeOrigin::signed(APPLICANT),
|
||||
H256::from_low_u64_be(12345),
|
||||
CITIZEN_1
|
||||
));
|
||||
assert_ok!(IdentityKycPallet::approve_referral(
|
||||
RuntimeOrigin::signed(CITIZEN_1),
|
||||
APPLICANT
|
||||
));
|
||||
assert_ok!(IdentityKycPallet::confirm_citizenship(RuntimeOrigin::signed(APPLICANT)));
|
||||
|
||||
assert_eq!(IdentityKycPallet::kyc_status_of(APPLICANT), KycLevel::Approved);
|
||||
|
||||
// Governance revokes
|
||||
assert_ok!(IdentityKycPallet::revoke_citizenship(RuntimeOrigin::root(), APPLICANT));
|
||||
|
||||
// Status should be Revoked
|
||||
assert_eq!(IdentityKycPallet::kyc_status_of(APPLICANT), KycLevel::Revoked);
|
||||
|
||||
// Event
|
||||
System::assert_last_event(Event::CitizenshipRevoked { who: APPLICANT }.into());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn revoke_citizenship_fails_for_bad_origin() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Non-root cannot revoke
|
||||
assert_noop!(
|
||||
IdentityKycPallet::revoke_citizenship(RuntimeOrigin::signed(CITIZEN_1), FOUNDER),
|
||||
DispatchError::BadOrigin
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn revoke_citizenship_fails_if_not_citizen() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// APPLICANT is not a citizen
|
||||
assert_noop!(
|
||||
IdentityKycPallet::revoke_citizenship(RuntimeOrigin::root(), APPLICANT),
|
||||
Error::<Test>::CannotRevokeInCurrentState
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// renounce_citizenship Tests (Voluntary exit)
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn renounce_citizenship_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// CITIZEN_1 is pre-approved, can renounce
|
||||
assert_eq!(IdentityKycPallet::kyc_status_of(CITIZEN_1), KycLevel::Approved);
|
||||
|
||||
assert_ok!(IdentityKycPallet::renounce_citizenship(RuntimeOrigin::signed(CITIZEN_1)));
|
||||
|
||||
// Status should be reset to NotStarted
|
||||
assert_eq!(IdentityKycPallet::kyc_status_of(CITIZEN_1), KycLevel::NotStarted);
|
||||
|
||||
// Identity hash should be removed
|
||||
assert!(IdentityKycPallet::identity_hash_of(CITIZEN_1).is_none());
|
||||
|
||||
// Event
|
||||
System::assert_last_event(Event::CitizenshipRenounced { who: CITIZEN_1 }.into());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renounce_citizenship_fails_if_not_citizen() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// APPLICANT is not a citizen
|
||||
assert_noop!(
|
||||
IdentityKycPallet::renounce_citizenship(RuntimeOrigin::signed(APPLICANT)),
|
||||
Error::<Test>::NotACitizen
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Full Workflow Tests
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn full_citizenship_workflow() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let identity_hash = H256::from_low_u64_be(12345);
|
||||
|
||||
// 1. Apply
|
||||
assert_ok!(IdentityKycPallet::apply_for_citizenship(
|
||||
RuntimeOrigin::signed(APPLICANT),
|
||||
identity_hash,
|
||||
CITIZEN_1
|
||||
));
|
||||
assert_eq!(IdentityKycPallet::kyc_status_of(APPLICANT), KycLevel::PendingReferral);
|
||||
|
||||
// 2. Referrer approves
|
||||
assert_ok!(IdentityKycPallet::approve_referral(
|
||||
RuntimeOrigin::signed(CITIZEN_1),
|
||||
APPLICANT
|
||||
));
|
||||
assert_eq!(IdentityKycPallet::kyc_status_of(APPLICANT), KycLevel::ReferrerApproved);
|
||||
|
||||
// 3. Self-confirm
|
||||
assert_ok!(IdentityKycPallet::confirm_citizenship(RuntimeOrigin::signed(APPLICANT)));
|
||||
assert_eq!(IdentityKycPallet::kyc_status_of(APPLICANT), KycLevel::Approved);
|
||||
|
||||
// 4. Now APPLICANT is a citizen and can be a referrer for others
|
||||
let new_user = 50;
|
||||
// First give new_user some balance
|
||||
Balances::make_free_balance_be(&new_user, 10_000);
|
||||
|
||||
assert_ok!(IdentityKycPallet::apply_for_citizenship(
|
||||
RuntimeOrigin::signed(new_user),
|
||||
H256::from_low_u64_be(99999),
|
||||
APPLICANT // APPLICANT is now the referrer
|
||||
));
|
||||
assert_eq!(IdentityKycPallet::kyc_status_of(new_user), KycLevel::PendingReferral);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renounce_and_reapply_workflow() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Complete first citizenship
|
||||
assert_ok!(IdentityKycPallet::apply_for_citizenship(
|
||||
RuntimeOrigin::signed(APPLICANT),
|
||||
H256::from_low_u64_be(12345),
|
||||
CITIZEN_1
|
||||
));
|
||||
assert_ok!(IdentityKycPallet::approve_referral(
|
||||
RuntimeOrigin::signed(CITIZEN_1),
|
||||
APPLICANT
|
||||
));
|
||||
assert_ok!(IdentityKycPallet::confirm_citizenship(RuntimeOrigin::signed(APPLICANT)));
|
||||
assert_eq!(IdentityKycPallet::kyc_status_of(APPLICANT), KycLevel::Approved);
|
||||
|
||||
// Renounce
|
||||
assert_ok!(IdentityKycPallet::renounce_citizenship(RuntimeOrigin::signed(APPLICANT)));
|
||||
assert_eq!(IdentityKycPallet::kyc_status_of(APPLICANT), KycLevel::NotStarted);
|
||||
|
||||
// Can reapply (free world principle)
|
||||
assert_ok!(IdentityKycPallet::apply_for_citizenship(
|
||||
RuntimeOrigin::signed(APPLICANT),
|
||||
H256::from_low_u64_be(99999), // Different hash
|
||||
FOUNDER // Different referrer
|
||||
));
|
||||
assert_eq!(IdentityKycPallet::kyc_status_of(APPLICANT), KycLevel::PendingReferral);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Helper Function Tests
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn is_citizen_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Founding citizens should return true
|
||||
assert!(IdentityKycPallet::is_citizen(&FOUNDER));
|
||||
assert!(IdentityKycPallet::is_citizen(&CITIZEN_1));
|
||||
|
||||
// Non-citizens should return false
|
||||
assert!(!IdentityKycPallet::is_citizen(&APPLICANT));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_referrer_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Complete citizenship for APPLICANT
|
||||
assert_ok!(IdentityKycPallet::apply_for_citizenship(
|
||||
RuntimeOrigin::signed(APPLICANT),
|
||||
H256::from_low_u64_be(12345),
|
||||
CITIZEN_1
|
||||
));
|
||||
assert_ok!(IdentityKycPallet::approve_referral(
|
||||
RuntimeOrigin::signed(CITIZEN_1),
|
||||
APPLICANT
|
||||
));
|
||||
assert_ok!(IdentityKycPallet::confirm_citizenship(RuntimeOrigin::signed(APPLICANT)));
|
||||
|
||||
// Should return the referrer
|
||||
assert_eq!(IdentityKycPallet::get_referrer(&APPLICANT), Some(CITIZEN_1));
|
||||
|
||||
// Founding citizens have no referrer (they were genesis)
|
||||
assert_eq!(IdentityKycPallet::get_referrer(&FOUNDER), None);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use pezframe_support::pezpallet_prelude::{BoundedVec, Get, RuntimeDebug};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_core::H256;
|
||||
|
||||
/// Citizenship status levels
|
||||
/// PRIVACY: No personal data stored on-chain, only status and hash
|
||||
#[derive(
|
||||
Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen, Copy, Default,
|
||||
)]
|
||||
pub enum KycLevel {
|
||||
/// No citizenship application
|
||||
#[default]
|
||||
NotStarted,
|
||||
/// Application submitted, waiting for referrer approval
|
||||
/// TRUSTLESS: Referrer must approve before self-confirmation
|
||||
PendingReferral,
|
||||
/// Referrer approved, waiting for applicant's self-confirmation
|
||||
/// TRUSTLESS: No admin involved, applicant confirms themselves
|
||||
ReferrerApproved,
|
||||
/// Approved citizen with full rights
|
||||
Approved,
|
||||
/// Citizenship revoked (by governance or self-renounce)
|
||||
Revoked,
|
||||
}
|
||||
|
||||
/// Privacy-preserving citizenship application
|
||||
/// SECURITY: No personal data on-chain, only hash
|
||||
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
pub struct CitizenshipApplication<AccountId> {
|
||||
/// Hash of identity documents (actual documents stored off-chain/IPFS)
|
||||
/// Frontend calculates: H256(name + email + document_cids)
|
||||
pub identity_hash: H256,
|
||||
/// The existing citizen who vouches for this applicant
|
||||
/// TRUSTLESS: Referrer is personally responsible for their referrals
|
||||
pub referrer: AccountId,
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Clone, Default, MaxEncodedLen)]
|
||||
pub struct IdentityInfo<MaxStringLength: Get<u32>> {
|
||||
pub name: BoundedVec<u8, MaxStringLength>,
|
||||
pub email: BoundedVec<u8, MaxStringLength>,
|
||||
}
|
||||
|
||||
// Manually implement PartialEq to avoid requiring `MaxStringLength: PartialEq`
|
||||
impl<MaxStringLength: Get<u32>> PartialEq for IdentityInfo<MaxStringLength> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.name == other.name && self.email == other.email
|
||||
}
|
||||
}
|
||||
impl<MaxStringLength: Get<u32>> Eq for IdentityInfo<MaxStringLength> {}
|
||||
|
||||
// Manually implement Debug as well for the same reason.
|
||||
impl<MaxStringLength: Get<u32>> core::fmt::Debug for IdentityInfo<MaxStringLength> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("IdentityInfo")
|
||||
.field("name", &self.name)
|
||||
.field("email", &self.email)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<MaxStringLength: Get<u32> + 'static> TypeInfo for IdentityInfo<MaxStringLength>
|
||||
where
|
||||
BoundedVec<u8, MaxStringLength>: TypeInfo,
|
||||
{
|
||||
type Identity = Self;
|
||||
|
||||
fn type_info() -> scale_info::Type {
|
||||
scale_info::Type::builder()
|
||||
.path(scale_info::Path::new("IdentityInfo", "pezpallet_identity_kyc::types"))
|
||||
.composite(
|
||||
scale_info::build::Fields::named()
|
||||
.field(|f| {
|
||||
f.ty::<BoundedVec<u8, MaxStringLength>>()
|
||||
.name("name")
|
||||
.type_name("BoundedVec<u8, MaxStringLength>")
|
||||
})
|
||||
.field(|f| {
|
||||
f.ty::<BoundedVec<u8, MaxStringLength>>()
|
||||
.name("email")
|
||||
.type_name("BoundedVec<u8, MaxStringLength>")
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Clone, Default, MaxEncodedLen)]
|
||||
pub struct KycApplication<MaxStringLength: Get<u32>, MaxCidLength: Get<u32>> {
|
||||
pub cids: BoundedVec<BoundedVec<u8, MaxCidLength>, MaxCidLength>,
|
||||
pub notes: BoundedVec<u8, MaxStringLength>,
|
||||
}
|
||||
|
||||
// Manually implement PartialEq to avoid requiring generic bounds to be PartialEq
|
||||
impl<MaxStringLength: Get<u32>, MaxCidLength: Get<u32>> PartialEq
|
||||
for KycApplication<MaxStringLength, MaxCidLength>
|
||||
{
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.cids == other.cids && self.notes == other.notes
|
||||
}
|
||||
}
|
||||
impl<MaxStringLength: Get<u32>, MaxCidLength: Get<u32>> Eq
|
||||
for KycApplication<MaxStringLength, MaxCidLength>
|
||||
{
|
||||
}
|
||||
|
||||
// Manually implement Debug as well for the same reason.
|
||||
impl<MaxStringLength: Get<u32>, MaxCidLength: Get<u32>> core::fmt::Debug
|
||||
for KycApplication<MaxStringLength, MaxCidLength>
|
||||
{
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("KycApplication")
|
||||
.field("cids", &self.cids)
|
||||
.field("notes", &self.notes)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<MaxStringLength: Get<u32> + 'static, MaxCidLength: Get<u32> + 'static> TypeInfo
|
||||
for KycApplication<MaxStringLength, MaxCidLength>
|
||||
where
|
||||
BoundedVec<BoundedVec<u8, MaxCidLength>, MaxCidLength>: TypeInfo,
|
||||
BoundedVec<u8, MaxStringLength>: TypeInfo,
|
||||
{
|
||||
type Identity = Self;
|
||||
|
||||
fn type_info() -> scale_info::Type {
|
||||
scale_info::Type::builder()
|
||||
.path(scale_info::Path::new("KycApplication", "pezpallet_identity_kyc::types"))
|
||||
.composite(
|
||||
scale_info::build::Fields::named()
|
||||
.field(|f| {
|
||||
f.ty::<BoundedVec<BoundedVec<u8, MaxCidLength>, MaxCidLength>>()
|
||||
.name("cids")
|
||||
.type_name("BoundedVec<BoundedVec<u8, MaxCidLength>, MaxCidLength>")
|
||||
})
|
||||
.field(|f| {
|
||||
f.ty::<BoundedVec<u8, MaxStringLength>>()
|
||||
.name("notes")
|
||||
.type_name("BoundedVec<u8, MaxStringLength>")
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
// --- Dış Dünya İçin Arayüzler (Traits) ---
|
||||
|
||||
/// Bir hesabın KYC durumunu sorgulamak için arayüz.
|
||||
pub trait KycStatus<AccountId> {
|
||||
fn get_kyc_status(who: &AccountId) -> KycLevel;
|
||||
}
|
||||
|
||||
/// Bir hesabın kimlik bilgilerini sorgulamak için arayüz.
|
||||
pub trait IdentityInfoProvider<AccountId, MaxStringLength: Get<u32>> {
|
||||
fn get_identity_info(who: &AccountId) -> Option<IdentityInfo<MaxStringLength>>;
|
||||
}
|
||||
|
||||
/// KYC onaylandığında tetiklenecek eylemleri tanımlayan arayüz.
|
||||
/// Bu trait identity-kyc palletinde tanımlanır ve diğer palletler (örn. referral)
|
||||
/// tarafından implement edilir, böylece circular dependency oluşmaz.
|
||||
///
|
||||
/// UPDATED (Gemini suggestion): Now includes referrer parameter to avoid
|
||||
/// data loss when identity-kyc and referral have separate storage.
|
||||
pub trait OnKycApproved<AccountId> {
|
||||
/// Called when a citizen is approved
|
||||
/// - `who`: The newly approved citizen
|
||||
/// - `referrer`: The citizen who vouched for them (from identity-kyc storage)
|
||||
fn on_kyc_approved(who: &AccountId, referrer: &AccountId);
|
||||
}
|
||||
|
||||
/// No-op implementation for when no hook is needed
|
||||
impl<AccountId> OnKycApproved<AccountId> for () {
|
||||
fn on_kyc_approved(_who: &AccountId, _referrer: &AccountId) {}
|
||||
}
|
||||
|
||||
/// Vatandaşlık NFT'si mintlemek için arayüz.
|
||||
/// Bu trait identity-kyc palletinde tanımlanır ve tiki pallet tarafından
|
||||
/// implement edilir, böylece circular dependency oluşmaz.
|
||||
pub trait CitizenNftProvider<AccountId> {
|
||||
fn mint_citizen_nft(who: &AccountId) -> pezsp_runtime::DispatchResult;
|
||||
|
||||
/// Mint citizen NFT with self-confirmation (uses force_mint internally)
|
||||
fn mint_citizen_nft_confirmed(who: &AccountId) -> pezsp_runtime::DispatchResult;
|
||||
|
||||
/// Burn citizen NFT when user renounces citizenship
|
||||
fn burn_citizen_nft(who: &AccountId) -> pezsp_runtime::DispatchResult;
|
||||
}
|
||||
|
||||
/// Hook called when citizenship is revoked (for direct responsibility penalty)
|
||||
/// Defined here to avoid circular dependency, implemented by referral pallet
|
||||
pub trait OnCitizenshipRevoked<AccountId> {
|
||||
fn on_citizenship_revoked(who: &AccountId);
|
||||
}
|
||||
|
||||
/// No-op implementation for when no hook is needed
|
||||
impl<AccountId> OnCitizenshipRevoked<AccountId> for () {
|
||||
fn on_citizenship_revoked(_who: &AccountId) {}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
//! Autogenerated weights for `pezpallet_identity_kyc`
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 32.0.0
|
||||
//! DATE: 2025-12-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `MamostePC`, CPU: `11th Gen Intel(R) Core(TM) i9-11950H @ 2.60GHz`
|
||||
//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024`
|
||||
|
||||
// Executed Command:
|
||||
// ./target/release/frame-omni-bencher
|
||||
// v1
|
||||
// benchmark
|
||||
// pallet
|
||||
// --runtime
|
||||
// target/release/wbuild/people-pezkuwichain-runtime/people_pezkuwichain_runtime.compact.compressed.wasm
|
||||
// --pallets
|
||||
// pezpallet_identity_kyc
|
||||
// -e
|
||||
// all
|
||||
// --steps
|
||||
// 50
|
||||
// --repeat
|
||||
// 20
|
||||
// --output
|
||||
// pezcumulus/teyrchains/pezpallets/identity-kyc/src/weights.rs
|
||||
// --template
|
||||
// bizinikiwi/.maintain/frame-weight-template.hbs
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(missing_docs)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use pezframe_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for `pezpallet_identity_kyc`.
|
||||
pub trait WeightInfo {
|
||||
fn apply_for_citizenship() -> Weight;
|
||||
fn approve_referral() -> Weight;
|
||||
fn confirm_citizenship() -> Weight;
|
||||
fn revoke_citizenship() -> Weight;
|
||||
fn renounce_citizenship() -> Weight;
|
||||
fn cancel_application() -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for `pezpallet_identity_kyc` using the Bizinikiwi node and recommended hardware.
|
||||
pub struct BizinikiwiWeight<T>(PhantomData<T>);
|
||||
impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
|
||||
/// Storage: `IdentityKyc::KycStatuses` (r:2 w:1)
|
||||
/// Proof: `IdentityKyc::KycStatuses` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `IdentityKyc::Applications` (r:0 w:1)
|
||||
/// Proof: `IdentityKyc::Applications` (`max_values`: None, `max_size`: Some(112), added: 2587, mode: `MaxEncodedLen`)
|
||||
fn apply_for_citizenship() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `253`
|
||||
// Estimated: `6038`
|
||||
// Minimum execution time: 32_436_000 picoseconds.
|
||||
Weight::from_parts(33_789_000, 6038)
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: `IdentityKyc::KycStatuses` (r:1 w:1)
|
||||
/// Proof: `IdentityKyc::KycStatuses` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`)
|
||||
/// Storage: `IdentityKyc::Applications` (r:1 w:0)
|
||||
/// Proof: `IdentityKyc::Applications` (`max_values`: None, `max_size`: Some(112), added: 2587, mode: `MaxEncodedLen`)
|
||||
fn approve_referral() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `323`
|
||||
// Estimated: `3577`
|
||||
// Minimum execution time: 17_647_000 picoseconds.
|
||||
Weight::from_parts(18_444_000, 3577)
|
||||
.saturating_add(T::DbWeight::get().reads(2_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `IdentityKyc::KycStatuses` (r:1 w:1)
|
||||
/// Proof: `IdentityKyc::KycStatuses` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`)
|
||||
/// Storage: `IdentityKyc::Applications` (r:1 w:1)
|
||||
/// Proof: `IdentityKyc::Applications` (`max_values`: None, `max_size`: Some(112), added: 2587, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `IdentityKyc::IdentityHashes` (r:0 w:1)
|
||||
/// Proof: `IdentityKyc::IdentityHashes` (`max_values`: None, `max_size`: Some(80), added: 2555, mode: `MaxEncodedLen`)
|
||||
/// Storage: `IdentityKyc::CitizenReferrers` (r:0 w:1)
|
||||
/// Proof: `IdentityKyc::CitizenReferrers` (`max_values`: None, `max_size`: Some(80), added: 2555, mode: `MaxEncodedLen`)
|
||||
fn confirm_citizenship() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `426`
|
||||
// Estimated: `3593`
|
||||
// Minimum execution time: 37_545_000 picoseconds.
|
||||
Weight::from_parts(40_069_000, 3593)
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(5_u64))
|
||||
}
|
||||
/// Storage: `IdentityKyc::KycStatuses` (r:1 w:1)
|
||||
/// Proof: `IdentityKyc::KycStatuses` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`)
|
||||
fn revoke_citizenship() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `150`
|
||||
// Estimated: `3514`
|
||||
// Minimum execution time: 12_919_000 picoseconds.
|
||||
Weight::from_parts(13_877_000, 3514)
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `IdentityKyc::KycStatuses` (r:1 w:1)
|
||||
/// Proof: `IdentityKyc::KycStatuses` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`)
|
||||
/// Storage: `IdentityKyc::IdentityHashes` (r:0 w:1)
|
||||
/// Proof: `IdentityKyc::IdentityHashes` (`max_values`: None, `max_size`: Some(80), added: 2555, mode: `MaxEncodedLen`)
|
||||
fn renounce_citizenship() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `150`
|
||||
// Estimated: `3514`
|
||||
// Minimum execution time: 14_510_000 picoseconds.
|
||||
Weight::from_parts(14_914_000, 3514)
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: `IdentityKyc::KycStatuses` (r:1 w:1)
|
||||
/// Proof: `IdentityKyc::KycStatuses` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `IdentityKyc::Applications` (r:0 w:1)
|
||||
/// Proof: `IdentityKyc::Applications` (`max_values`: None, `max_size`: Some(112), added: 2587, mode: `MaxEncodedLen`)
|
||||
fn cancel_application() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `299`
|
||||
// Estimated: `3593`
|
||||
// Minimum execution time: 28_146_000 picoseconds.
|
||||
Weight::from_parts(29_261_000, 3593)
|
||||
.saturating_add(T::DbWeight::get().reads(2_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(3_u64))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests.
|
||||
impl WeightInfo for () {
|
||||
/// Storage: `IdentityKyc::KycStatuses` (r:2 w:1)
|
||||
/// Proof: `IdentityKyc::KycStatuses` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `IdentityKyc::Applications` (r:0 w:1)
|
||||
/// Proof: `IdentityKyc::Applications` (`max_values`: None, `max_size`: Some(112), added: 2587, mode: `MaxEncodedLen`)
|
||||
fn apply_for_citizenship() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `253`
|
||||
// Estimated: `6038`
|
||||
// Minimum execution time: 32_436_000 picoseconds.
|
||||
Weight::from_parts(33_789_000, 6038)
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: `IdentityKyc::KycStatuses` (r:1 w:1)
|
||||
/// Proof: `IdentityKyc::KycStatuses` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`)
|
||||
/// Storage: `IdentityKyc::Applications` (r:1 w:0)
|
||||
/// Proof: `IdentityKyc::Applications` (`max_values`: None, `max_size`: Some(112), added: 2587, mode: `MaxEncodedLen`)
|
||||
fn approve_referral() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `323`
|
||||
// Estimated: `3577`
|
||||
// Minimum execution time: 17_647_000 picoseconds.
|
||||
Weight::from_parts(18_444_000, 3577)
|
||||
.saturating_add(RocksDbWeight::get().reads(2_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `IdentityKyc::KycStatuses` (r:1 w:1)
|
||||
/// Proof: `IdentityKyc::KycStatuses` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`)
|
||||
/// Storage: `IdentityKyc::Applications` (r:1 w:1)
|
||||
/// Proof: `IdentityKyc::Applications` (`max_values`: None, `max_size`: Some(112), added: 2587, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `IdentityKyc::IdentityHashes` (r:0 w:1)
|
||||
/// Proof: `IdentityKyc::IdentityHashes` (`max_values`: None, `max_size`: Some(80), added: 2555, mode: `MaxEncodedLen`)
|
||||
/// Storage: `IdentityKyc::CitizenReferrers` (r:0 w:1)
|
||||
/// Proof: `IdentityKyc::CitizenReferrers` (`max_values`: None, `max_size`: Some(80), added: 2555, mode: `MaxEncodedLen`)
|
||||
fn confirm_citizenship() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `426`
|
||||
// Estimated: `3593`
|
||||
// Minimum execution time: 37_545_000 picoseconds.
|
||||
Weight::from_parts(40_069_000, 3593)
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(5_u64))
|
||||
}
|
||||
/// Storage: `IdentityKyc::KycStatuses` (r:1 w:1)
|
||||
/// Proof: `IdentityKyc::KycStatuses` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`)
|
||||
fn revoke_citizenship() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `150`
|
||||
// Estimated: `3514`
|
||||
// Minimum execution time: 12_919_000 picoseconds.
|
||||
Weight::from_parts(13_877_000, 3514)
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `IdentityKyc::KycStatuses` (r:1 w:1)
|
||||
/// Proof: `IdentityKyc::KycStatuses` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`)
|
||||
/// Storage: `IdentityKyc::IdentityHashes` (r:0 w:1)
|
||||
/// Proof: `IdentityKyc::IdentityHashes` (`max_values`: None, `max_size`: Some(80), added: 2555, mode: `MaxEncodedLen`)
|
||||
fn renounce_citizenship() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `150`
|
||||
// Estimated: `3514`
|
||||
// Minimum execution time: 14_510_000 picoseconds.
|
||||
Weight::from_parts(14_914_000, 3514)
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: `IdentityKyc::KycStatuses` (r:1 w:1)
|
||||
/// Proof: `IdentityKyc::KycStatuses` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `IdentityKyc::Applications` (r:0 w:1)
|
||||
/// Proof: `IdentityKyc::Applications` (`max_values`: None, `max_size`: Some(112), added: 2587, mode: `MaxEncodedLen`)
|
||||
fn cancel_application() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `299`
|
||||
// Estimated: `3593`
|
||||
// Minimum execution time: 28_146_000 picoseconds.
|
||||
Weight::from_parts(29_261_000, 3593)
|
||||
.saturating_add(RocksDbWeight::get().reads(2_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user