From 552200cdfce9922d85fa6fb633dd655f9fde2c65 Mon Sep 17 00:00:00 2001 From: Robert Zaremba Date: Tue, 22 Aug 2023 11:52:02 +0200 Subject: [PATCH 1/3] feat(registry): is_community_verified --- contracts/registry/src/lib.rs | 95 ++++++++++++++++++++++++------- contracts/registry/src/migrate.rs | 4 +- contracts/registry/src/storage.rs | 2 +- 3 files changed, 79 insertions(+), 22 deletions(-) diff --git a/contracts/registry/src/lib.rs b/contracts/registry/src/lib.rs index 8f389e7..b3da5d7 100644 --- a/contracts/registry/src/lib.rs +++ b/contracts/registry/src/lib.rs @@ -49,9 +49,12 @@ pub struct Contract { pub(crate) next_token_ids: LookupMap, pub(crate) next_issuer_id: IssuerId, - /// tuple of (required issuer for IAH, [required list of classes for IAH]) - /// represents mandatory requirements to be verified as human by using is_human and is_human_call methods. + /// tuple of (required issuer, [required list of classes]) that represents mandatory + /// requirements to be verified as human for `is_human` and `is_human_call` methods. pub(crate) iah_sbts: (AccountId, Vec), + /// Class Set that that represents mandatory requirements to be community verified for using + /// `is_community_verified` and `is_community_verified_call` methods. + pub(crate) community_verified_set: ClassSet, } // Implement the contract structure @@ -65,6 +68,7 @@ impl Contract { authority: AccountId, iah_issuer: AccountId, iah_classes: Vec, + community_verified_set: ClassSet, authorized_flaggers: Vec, ) -> Self { require!( @@ -90,6 +94,7 @@ impl Contract { StorageKey::AdminsFlagged, Some(&authorized_flaggers), ), + community_verified_set, }; contract._add_sbt_issuer(&iah_issuer); contract @@ -109,6 +114,11 @@ impl Contract { vec![self.iah_sbts.clone()] } + /// Returns Community Verified class set. + pub fn community_verified_class_set(&self) -> ClassSet { + self.community_verified_set.clone() + } + #[inline] fn _is_banned(&self, account: &AccountId) -> bool { self.banlist.contains(account) @@ -123,35 +133,80 @@ impl Contract { /// Otherwise returns list of SBTs (identifed by issuer and list of token IDs) proving /// the `account` humanity. pub fn is_human(&self, account: AccountId) -> SBTs { - self._is_human(&account) + self._is_human(&account, false) } - fn _is_human(&self, account: &AccountId) -> SBTs { - if self._is_banned(&account) { - return vec![]; + fn _is_human(&self, account: &AccountId, no_skip_ban: bool) -> SBTs { + if no_skip_ban { + if self.flagged.get(&account) == Some(AccountFlag::Blacklisted) + || self._is_banned(&account) + { + return vec![]; + } } - if let Some(AccountFlag::Blacklisted) = self.flagged.get(&account) { - return vec![]; + + let proof = self._list_sbts_by_classes(account, &self.iah_sbts.0, &self.iah_sbts.1); + vec![(self.iah_sbts.0.clone(), proof)] + } + + /// Returns tuple of community_verified proof and IAH proof. + /// If `is_human` is false, then the `is_human` check is skipped and empty list is + /// returned. Otherwise `is_human` is assumed to be required and if the IAH check fails, + /// the community verification is skipped (so two empty lists are returned). + pub fn is_community_verified(&self, account: AccountId, is_human: bool) -> (SBTs, SBTs) { + self._is_community_verified(&account, is_human) + } + + fn _is_community_verified(&self, account: &AccountId, is_human: bool) -> (SBTs, SBTs) { + if self.flagged.get(&account) == Some(AccountFlag::Blacklisted) || self._is_banned(&account) + { + return (vec![], vec![]); } - let issuer = Some(self.iah_sbts.0.clone()); - let mut proof: Vec = Vec::new(); - // check if user has tokens from all classes - for cls in &self.iah_sbts.1 { + let iah_proof = if is_human { + let iah_proof = self._is_human(account, true); + // iah is required, but user is not human. Early return. + if iah_proof.is_empty() { + return (vec![], iah_proof); + } + iah_proof + } else { + vec![] + }; + + let mut proof: SBTs = Vec::new(); + for (issuer, classes) in &self.community_verified_set { + let issuer_proof = self._list_sbts_by_classes(account, issuer, classes); + if issuer_proof.is_empty() { + return (vec![], iah_proof); + } + proof.push((issuer.clone(), issuer_proof)); + } + (proof, iah_proof) + } + + fn _list_sbts_by_classes( + &self, + acc: &AccountId, + issuer: &AccountId, + cls: &Vec, + ) -> Vec { + let mut cls_sbts: Vec = Vec::new(); + for c in cls { let tokens = self.sbt_tokens_by_owner( - account.clone(), - issuer.clone(), - Some(*cls), + acc.clone(), + Some(issuer.clone()), + Some(*c), Some(1), None, ); // we need to check class, because the query can return a "next" token if a user // doesn't have the token of requested class. - if !(tokens.len() > 0 && tokens[0].1[0].metadata.class == *cls) { + if !(tokens.len() > 0 && tokens[0].1[0].metadata.class == *c) { return vec![]; } - proof.push(tokens[0].1[0].token) + cls_sbts.push(tokens[0].1[0].token) } - vec![(self.iah_sbts.0.clone(), proof)] + cls_sbts } // @@ -168,7 +223,7 @@ impl Contract { let issuer = &env::predecessor_account_id(); for ts in &token_spec { require!( - !self._is_human(&ts.0).is_empty(), + !self._is_human(&ts.0, false).is_empty(), format!("{} is not a human", &ts.0) ); } @@ -291,7 +346,7 @@ impl Contract { #[payable] pub fn is_human_call(&mut self, ctr: AccountId, function: String, payload: String) -> Promise { let caller = env::predecessor_account_id(); - let iah_proof = self._is_human(&caller); + let iah_proof = self._is_human(&caller, false); require!(!iah_proof.is_empty(), "caller not a human"); let args = IsHumanCallbackArgs { diff --git a/contracts/registry/src/migrate.rs b/contracts/registry/src/migrate.rs index bbd388c..9ecba58 100644 --- a/contracts/registry/src/migrate.rs +++ b/contracts/registry/src/migrate.rs @@ -22,11 +22,12 @@ pub struct OldState { impl Contract { #[private] #[init(ignore_state)] - pub fn migrate(authorized_flaggers: Vec) -> Self { + pub fn migrate(community_verified_set: ClassSet, authorized_flaggers: Vec) -> Self { let old_state: OldState = env::state_read().expect("failed"); // new field in the smart contract : // + flagged: LookupMap // + admins_flagged: LazyOption> + // + community_verified_set Self { authority: old_state.authority.clone(), @@ -47,6 +48,7 @@ impl Contract { StorageKey::AdminsFlagged, Some(&authorized_flaggers), ), + community_verified_set, } } } diff --git a/contracts/registry/src/storage.rs b/contracts/registry/src/storage.rs index d689a40..3389f1c 100644 --- a/contracts/registry/src/storage.rs +++ b/contracts/registry/src/storage.rs @@ -24,7 +24,7 @@ pub enum StorageKey { AdminsFlagged, } -#[derive(BorshSerialize, BorshDeserialize, BorshStorageKey, Serialize, Deserialize)] +#[derive(BorshSerialize, BorshDeserialize, BorshStorageKey, Serialize, Deserialize, PartialEq)] #[serde(crate = "near_sdk::serde")] #[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] pub enum AccountFlag { From a580eb59318e491ed8c036d26e631e94a399132d Mon Sep 17 00:00:00 2001 From: Robert Zaremba Date: Tue, 22 Aug 2023 13:29:59 +0200 Subject: [PATCH 2/3] update is_community_verified semantics --- contracts/registry/src/lib.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/contracts/registry/src/lib.rs b/contracts/registry/src/lib.rs index b3da5d7..44ecd01 100644 --- a/contracts/registry/src/lib.rs +++ b/contracts/registry/src/lib.rs @@ -151,8 +151,8 @@ impl Contract { /// Returns tuple of community_verified proof and IAH proof. /// If `is_human` is false, then the `is_human` check is skipped and empty list is - /// returned. Otherwise `is_human` is assumed to be required and if the IAH check fails, - /// the community verification is skipped (so two empty lists are returned). + /// returned. + /// EXPERIMENTAL: the function API can change in the future. pub fn is_community_verified(&self, account: AccountId, is_human: bool) -> (SBTs, SBTs) { self._is_community_verified(&account, is_human) } @@ -163,12 +163,7 @@ impl Contract { return (vec![], vec![]); } let iah_proof = if is_human { - let iah_proof = self._is_human(account, true); - // iah is required, but user is not human. Early return. - if iah_proof.is_empty() { - return (vec![], iah_proof); - } - iah_proof + self._is_human(account, true) } else { vec![] }; @@ -177,9 +172,8 @@ impl Contract { for (issuer, classes) in &self.community_verified_set { let issuer_proof = self._list_sbts_by_classes(account, issuer, classes); if issuer_proof.is_empty() { - return (vec![], iah_proof); + proof.push((issuer.clone(), issuer_proof)); } - proof.push((issuer.clone(), issuer_proof)); } (proof, iah_proof) } From 3a11f7687d019f77360acff5c2f132bc025bb7b2 Mon Sep 17 00:00:00 2001 From: Robert Zaremba Date: Tue, 22 Aug 2023 13:44:27 +0200 Subject: [PATCH 3/3] fix --- contracts/registry/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/registry/src/lib.rs b/contracts/registry/src/lib.rs index 44ecd01..55a6419 100644 --- a/contracts/registry/src/lib.rs +++ b/contracts/registry/src/lib.rs @@ -171,7 +171,7 @@ impl Contract { let mut proof: SBTs = Vec::new(); for (issuer, classes) in &self.community_verified_set { let issuer_proof = self._list_sbts_by_classes(account, issuer, classes); - if issuer_proof.is_empty() { + if !issuer_proof.is_empty() { proof.push((issuer.clone(), issuer_proof)); } }