From 19d857d37cab69b8adb9826d0747dda41a74ca23 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Thu, 9 Jun 2022 16:59:38 +0200 Subject: [PATCH 001/138] wip: skeleton in place --- Cargo.lock | 21 ++ pallets/attestation/src/lib.rs | 2 +- pallets/public-credentials/Cargo.toml | 75 ++++++++ pallets/public-credentials/src/credentials.rs | 37 ++++ .../public-credentials/src/default_weights.rs | 114 +++++++++++ pallets/public-credentials/src/lib.rs | 179 ++++++++++++++++++ runtimes/common/src/lib.rs | 2 +- 7 files changed, 428 insertions(+), 2 deletions(-) create mode 100644 pallets/public-credentials/Cargo.toml create mode 100644 pallets/public-credentials/src/credentials.rs create mode 100644 pallets/public-credentials/src/default_weights.rs create mode 100644 pallets/public-credentials/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 6e04fa1384..fbe79418df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8257,6 +8257,27 @@ dependencies = [ "cc", ] +[[package]] +name = "public-credentials" +version = "1.6.2" +dependencies = [ + "ctype", + "frame-benchmarking", + "frame-support", + "frame-system", + "kilt-support", + "log", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-keystore", + "sp-runtime", + "sp-std", +] + [[package]] name = "quick-error" version = "1.2.3" diff --git a/pallets/attestation/src/lib.rs b/pallets/attestation/src/lib.rs index 707a508b0c..76fc9ea6e4 100644 --- a/pallets/attestation/src/lib.rs +++ b/pallets/attestation/src/lib.rs @@ -111,7 +111,7 @@ pub mod pallet { pub type ClaimHashOf = ::Hash; /// Type of an attester identifier. - pub(crate) type AttesterOf = ::AttesterId; + pub type AttesterOf = ::AttesterId; /// Authorization id type pub(crate) type AuthorizationIdOf = ::AuthorizationId; diff --git a/pallets/public-credentials/Cargo.toml b/pallets/public-credentials/Cargo.toml new file mode 100644 index 0000000000..674bc63c9e --- /dev/null +++ b/pallets/public-credentials/Cargo.toml @@ -0,0 +1,75 @@ +[package] +authors = ["KILT "] +description = "Enables adding and revoking public credentials." +edition = "2021" +name = "public-credentials" +repository = "https://github.com/KILTprotocol/mashnet-node" +version = "1.6.2" + +[dev-dependencies] +# attestation = {features = ["mock"], path = "../attestation"} +ctype = {features = ["mock"], path = "../ctype"} +kilt-support = {features = ["mock"], path = "../../support"} + +pallet-balances = {branch = "polkadot-v0.9.23", git = "https://github.com/paritytech/substrate"} +serde = "1.0.137" +sp-core = {branch = "polkadot-v0.9.23", git = "https://github.com/paritytech/substrate"} +sp-io = {branch = "polkadot-v0.9.23", git = "https://github.com/paritytech/substrate"} +sp-keystore = {branch = "polkadot-v0.9.23", git = "https://github.com/paritytech/substrate"} + +[dependencies] +codec = {package = "parity-scale-codec", version = "3.1.2", default-features = false, features = ["derive"]} +log = "0.4.17" +scale-info = {version = "2.1.1", default-features = false, features = ["derive"]} +serde = {version = "1.0.137", optional = true} + +# Internal dependencies +# attestation = {default-features = false, path = "../attestation"} +ctype = {default-features = false, path = "../ctype"} +kilt-support = {default-features = false, path = "../../support"} + +#External dependencies +frame-benchmarking = {branch = "polkadot-v0.9.23", default-features = false, git = "https://github.com/paritytech/substrate", optional = true} +frame-support = {branch = "polkadot-v0.9.23", default-features = false, git = "https://github.com/paritytech/substrate"} +frame-system = {branch = "polkadot-v0.9.23", default-features = false, git = "https://github.com/paritytech/substrate"} +pallet-balances = {branch = "polkadot-v0.9.23", default-features = false, git = "https://github.com/paritytech/substrate", optional = true} +sp-core = {branch = "polkadot-v0.9.23", default-features = false, git = "https://github.com/paritytech/substrate", optional = true} +sp-io = {branch = "polkadot-v0.9.23", default-features = false, git = "https://github.com/paritytech/substrate", optional = true} +sp-keystore = {branch = "polkadot-v0.9.23", default-features = false, git = "https://github.com/paritytech/substrate", optional = true} +sp-runtime = {branch = "polkadot-v0.9.23", default-features = false, git = "https://github.com/paritytech/substrate"} +sp-std = {branch = "polkadot-v0.9.23", default-features = false, git = "https://github.com/paritytech/substrate"} + +[features] +default = ["std"] +mock = [ + "pallet-balances", + "serde", + "sp-core", + "sp-io", + "sp-keystore", +] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "kilt-support/runtime-benchmarks", + "sp-core", +] +std = [ + "codec/std", + # "attestation/std", + "ctype/std", + "frame-support/std", + "frame-system/std", + "kilt-support/std", + "log/std", + "pallet-balances/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +try-runtime = [ + "frame-support/try-runtime", +] diff --git a/pallets/public-credentials/src/credentials.rs b/pallets/public-credentials/src/credentials.rs new file mode 100644 index 0000000000..89098b62be --- /dev/null +++ b/pallets/public-credentials/src/credentials.rs @@ -0,0 +1,37 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2022 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +use frame_support::RuntimeDebug; + +#[derive(Encode, Decode, Clone, MaxEncodedLen, RuntimeDebug, PartialEq, Eq, PartialOrd, Ord, TypeInfo)] +pub struct Claim { + pub ctype_hash: CtypeHash, + pub subject: SubjectIdentifier, + pub contents: Content, +} + +#[derive(Encode, Decode, Clone, MaxEncodedLen, RuntimeDebug, PartialEq, Eq, PartialOrd, Ord, TypeInfo)] +pub struct Credential { + pub claim: Claim, + pub claimer_signature: Option, + pub nonce: Option, + pub claim_hash: ClaimHash, +} diff --git a/pallets/public-credentials/src/default_weights.rs b/pallets/public-credentials/src/default_weights.rs new file mode 100644 index 0000000000..10634e2a98 --- /dev/null +++ b/pallets/public-credentials/src/default_weights.rs @@ -0,0 +1,114 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2022 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +//! Autogenerated weights for attestation +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2021-10-15, STEPS: {{cmd.steps}}\, REPEAT: {{cmd.repeat}}\, LOW RANGE: {{cmd.lowest_range_values}}\, HIGH RANGE: {{cmd.highest_range_values}}\ +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// ./target/release/kilt-parachain +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=attestation +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./pallets/attestation/src/default_weights.rs +// --template=.maintain/weight-template.hbs + + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for attestation. +pub trait WeightInfo { + fn add() -> Weight; + fn revoke() -> Weight; + fn remove() -> Weight; + fn reclaim_deposit() -> Weight; +} + +/// Weights for attestation using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + fn add() -> Weight { + (74_496_000_u64) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + fn revoke() -> Weight { + (37_029_000_u64) + // Standard Error: 44_000 + .saturating_add(6_325_000_u64) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + fn remove() -> Weight { + (64_058_000_u64) + // Standard Error: 44_000 + .saturating_add(6_317_000_u64) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + fn reclaim_deposit() -> Weight { + (56_873_000_u64) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn add() -> Weight { + (74_496_000_u64) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + fn revoke() -> Weight { + (37_029_000_u64) + // Standard Error: 44_000 + .saturating_add(6_325_000_u64) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + fn remove() -> Weight { + (64_058_000_u64) + // Standard Error: 44_000 + .saturating_add(6_317_000_u64) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + fn reclaim_deposit() -> Weight { + (56_873_000_u64) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } +} diff --git a/pallets/public-credentials/src/lib.rs b/pallets/public-credentials/src/lib.rs new file mode 100644 index 0000000000..1dff59ce9e --- /dev/null +++ b/pallets/public-credentials/src/lib.rs @@ -0,0 +1,179 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2022 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +//! # Public credentials Pallet +//! +//! Provides means of issuing public KILT credentials on chain and revoking +//! them. +//! +//! - [`Config`] +//! - [`Call`] +//! - [`Pallet`] +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod credentials; +pub mod default_weights; + +pub use crate::{credentials::*, pallet::*, default_weights::WeightInfo}; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + use codec::MaxEncodedLen; + + use frame_support::{pallet_prelude::*, traits::{StorageVersion, IsType, ConstU32}, BoundedVec}; + use kilt_support::{signature::VerifySignature, traits::CallSources}; + + use ctype::CtypeHashOf; + use sp_core::H256; + + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + + pub(crate) type AccountIdOf = ::AccountId; + // TODO: Replace with an enum that includes KILT DIDs and asset DIDs. + pub(crate) type SubjectIdOf = AccountIdOf; + // FIXME: replace with ClaimHashOf from attestation pallet + pub(crate) type ClaimHash = BoundedVec>; + + pub type CredentialOf = Credential< + CtypeHashOf, + SubjectIdOf, + BoundedVec::MaxEncodedClaimContentLength>, + ::CredentialSignature, + ClaimHash, + H256, + >; + + #[pallet::config] + pub trait Config: frame_system::Config { + type CredentialClaimerIdentifier: Parameter + MaxEncodedLen; + type CredentialSignatureVerification: VerifySignature< + SignerId = Self::CredentialClaimerIdentifier, + Payload = ClaimHash, + Signature = Self::CredentialSignature, + >; + type CredentialSignature: Parameter; + type Event: From> + IsType<::Event>; + #[pallet::constant] + type MaxEncodedClaimContentLength: Get; + // FIXME: replace with AttesterOf from attestation pallet + type OriginSuccess: CallSources, AccountIdOf>; + type WeightInfo: WeightInfo; + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(_); + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + TestEvent, + } + + #[pallet::error] + pub enum Error { + TestError, + } +} + +// pub mod assets; + +// #[frame_support::pallet] +// pub mod pallet { +// use codec::MaxEncodedLen; +// use frame_support::{ +// dispatch::DispatchResult, +// pallet_prelude::*, +// traits::{ConstU32, Get, StorageVersion}, +// Blake2_128Concat, Twox64Concat, +// }; +// use frame_system::pallet_prelude::{BlockNumberFor, *}; +// use sp_core::H256; + +// use crate::credentials::{Claim, Credential}; +// use attestation::{Attestations, AttesterOf, ClaimHashOf}; +// use ctype::{CtypeHashOf, Ctypes}; +// use kilt_support::{signature::VerifySignature, traits::CallSources}; + +// #[pallet::config] +// pub trait Config: frame_system::Config + attestation::Config { +// type EnsureOrigin: EnsureOrigin< +// Success = ::OriginSuccess, +// ::Origin, +// >; + // type CredentialSignatureVerification: VerifySignature< + // SignerId = Self::CredentialClaimerIdentifier, + // Payload = ClaimHashOf, + // Signature = Self::CredentialSignature, + // >; + // type CredentialSignature: Parameter; + // type CredentialClaimerIdentifier: Parameter + MaxEncodedLen; +// type Event: From> + IsType<::Event>; + // type OriginSuccess: CallSources, AttesterOf>; +// type WeightInfo: WeightInfo; +// } + +// #[pallet::storage] +// #[pallet::getter(fn get_credential_info)] +// pub type Credentials = +// StorageDoubleMap<_, Twox64Concat, SubjectIdOf, Blake2_128Concat, ClaimHashOf, BlockNumberFor>; + +// #[pallet::hooks] +// impl Hooks> for Pallet {} + +// #[pallet::call] +// impl Pallet { +// #[pallet::weight(0)] +// pub fn add(origin: OriginFor, credential: CredentialOf) -> DispatchResult { +// let source = ::EnsureOrigin::ensure_origin(origin)?; +// let attester = source.subject(); +// let payer = source.sender(); + +// let Credential { +// claim: Claim { +// ctype_hash, +// subject, +// contents, +// }, +// claimer_signature, +// nonce, +// claim_hash, +// } = credential; + +// // Check that a CType exists. +// ensure!( +// Ctypes::::contains_key(&credential.claim.ctype_hash), +// // FIXME +// Error::::Test +// ); + +// // Check that an attestation with the same hash does NOT exist. +// ensure!( +// !Attestations::::contains_key(&credential.claim_hash), +// // FIXME +// Error::::Test +// ); + +// Ok(()) +// } +// } +// } diff --git a/runtimes/common/src/lib.rs b/runtimes/common/src/lib.rs index 70f5cb27e8..f7a1d8119c 100644 --- a/runtimes/common/src/lib.rs +++ b/runtimes/common/src/lib.rs @@ -30,7 +30,7 @@ pub use opaque::*; pub use frame_support::weights::constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; use frame_support::{ parameter_types, - traits::{Contains, ContainsLengthBound, Currency, Get, SortedMembers}, + traits::{Contains, ContainsLengthBound, Currency, Get, OnRuntimeUpgrade, SortedMembers}, weights::DispatchClass, }; use frame_system::limits; From 98c24cea82df74726cfa0c0fd6c9433defe11326 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Fri, 10 Jun 2022 14:59:48 +0200 Subject: [PATCH 002/138] wip: trying to fight with rust-analyzer --- Cargo.lock | 1 + pallets/attestation/src/lib.rs | 208 ++++++++------ pallets/public-credentials/Cargo.toml | 4 +- pallets/public-credentials/src/credentials.rs | 11 +- pallets/public-credentials/src/lib.rs | 253 +++++++++++------- 5 files changed, 283 insertions(+), 194 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fbe79418df..aadba2ca97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8261,6 +8261,7 @@ dependencies = [ name = "public-credentials" version = "1.6.2" dependencies = [ + "attestation", "ctype", "frame-benchmarking", "frame-support", diff --git a/pallets/attestation/src/lib.rs b/pallets/attestation/src/lib.rs index 76fc9ea6e4..7924ed047b 100644 --- a/pallets/attestation/src/lib.rs +++ b/pallets/attestation/src/lib.rs @@ -253,21 +253,124 @@ pub mod pallet { let source = ::EnsureOrigin::ensure_origin(origin)?; let payer = source.sender(); let who = source.subject(); + + Self::write_attestation(ctype_hash, claim_hash, who, payer, authorization) + } + + /// Revoke an existing attestation. + /// + /// The revoker must be either the creator of the attestation being + /// revoked or an entity that in the delegation tree is an ancestor of + /// the attester, i.e., it was either the delegator of the attester or + /// an ancestor thereof. + /// + /// Emits `AttestationRevoked`. + /// + /// # + /// Weight: O(P) where P is the number of steps required to verify that + /// the dispatch Origin controls the delegation entitled to revoke the + /// attestation. It is bounded by `max_parent_checks`. + /// - Reads: [Origin Account], Attestations, delegation::Roots + /// - Reads per delegation step P: delegation::Delegations + /// - Writes: Attestations, DelegatedAttestations + /// # + #[pallet::weight( + ::WeightInfo::revoke() + .saturating_add(authorization.as_ref().map(|ac| ac.can_revoke_weight()).unwrap_or(0)) + )] + pub fn revoke( + origin: OriginFor, + claim_hash: ClaimHashOf, + authorization: Option, + ) -> DispatchResultWithPostInfo { + let source = ::EnsureOrigin::ensure_origin(origin)?; + let who = source.subject(); + + Self::revoke_attestation(who, claim_hash, authorization) + } + + /// Remove an attestation. + /// + /// The origin must be either the creator of the attestation or an + /// entity which is an ancestor of the attester in the delegation tree, + /// i.e., it was either the delegator of the attester or an ancestor + /// thereof. + /// + /// Emits `AttestationRemoved`. + /// + /// # + /// Weight: O(P) where P is the number of steps required to verify that + /// the dispatch Origin controls the delegation entitled to revoke the + /// attestation. It is bounded by `max_parent_checks`. + /// - Reads: [Origin Account], Attestations, delegation::Roots + /// - Reads per delegation step P: delegation::Delegations + /// - Writes: Attestations, DelegatedAttestations + /// # + #[pallet::weight( + ::WeightInfo::remove() + .saturating_add(authorization.as_ref().map(|ac| ac.can_remove_weight()).unwrap_or(0)) + )] + pub fn remove( + origin: OriginFor, + claim_hash: ClaimHashOf, + authorization: Option, + ) -> DispatchResultWithPostInfo { + let source = ::EnsureOrigin::ensure_origin(origin)?; + let who = source.subject(); + + Self::remove_attestation(who, claim_hash, authorization) + } + + /// Reclaim a storage deposit by removing an attestation + /// + /// Emits `DepositReclaimed`. + /// + /// # + /// Weight: O(1) + /// - Reads: [Origin Account], Attestations, DelegatedAttestations + /// - Writes: Attestations, DelegatedAttestations + /// # + #[pallet::weight(::WeightInfo::reclaim_deposit())] + pub fn reclaim_deposit(origin: OriginFor, claim_hash: ClaimHashOf) -> DispatchResult { + let who = ensure_signed(origin)?; + let attestation = Attestations::::get(&claim_hash).ok_or(Error::::AttestationNotFound)?; + + ensure!(attestation.deposit.owner == who, Error::::Unauthorized); + + // *** No Fail beyond this point *** + + log::debug!("removing Attestation"); + + Self::remove_attestation_entry(attestation, claim_hash); + Self::deposit_event(Event::DepositReclaimed(who, claim_hash)); + + Ok(()) + } + } + + impl Pallet { + pub fn write_attestation( + ctype_hash: CtypeHashOf, + claim_hash: ClaimHashOf, + attester: AttesterOf, + payer: AccountIdOf, + authorization: Option, + ) -> DispatchResult { let deposit_amount = ::Deposit::get(); ensure!( - >::contains_key(&ctype_hash), + >::contains_key(&ctype_hash), ctype::Error::::CTypeNotFound ); ensure!( - !>::contains_key(&claim_hash), + !Attestations::::contains_key(&claim_hash), Error::::AlreadyAttested ); // Check for validity of the delegation node if specified. authorization .as_ref() - .map(|ac| ac.can_attest(&who, &ctype_hash, &claim_hash)) + .map(|ac| ac.can_attest(&attester, &ctype_hash, &claim_hash)) .transpose()?; let authorization_id = authorization.as_ref().map(|ac| ac.authorization_id()); @@ -281,7 +384,7 @@ pub mod pallet { &claim_hash, AttestationDetails { ctype_hash, - attester: who.clone(), + attester: attester.clone(), authorization_id: authorization_id.clone(), revoked: false, deposit, @@ -291,41 +394,22 @@ pub mod pallet { ExternalAttestations::::insert(authorization_id, claim_hash, true); } - Self::deposit_event(Event::AttestationCreated(who, claim_hash, ctype_hash, authorization_id)); + Self::deposit_event(Event::AttestationCreated( + attester, + claim_hash, + ctype_hash, + authorization_id, + )); Ok(()) } - /// Revoke an existing attestation. - /// - /// The revoker must be either the creator of the attestation being - /// revoked or an entity that in the delegation tree is an ancestor of - /// the attester, i.e., it was either the delegator of the attester or - /// an ancestor thereof. - /// - /// Emits `AttestationRevoked`. - /// - /// # - /// Weight: O(P) where P is the number of steps required to verify that - /// the dispatch Origin controls the delegation entitled to revoke the - /// attestation. It is bounded by `max_parent_checks`. - /// - Reads: [Origin Account], Attestations, delegation::Roots - /// - Reads per delegation step P: delegation::Delegations - /// - Writes: Attestations, DelegatedAttestations - /// # - #[pallet::weight( - ::WeightInfo::revoke() - .saturating_add(authorization.as_ref().map(|ac| ac.can_revoke_weight()).unwrap_or(0)) - )] - pub fn revoke( - origin: OriginFor, + pub fn revoke_attestation( + who: AttesterOf, claim_hash: ClaimHashOf, authorization: Option, ) -> DispatchResultWithPostInfo { - let source = ::EnsureOrigin::ensure_origin(origin)?; - let who = source.subject(); - - let attestation = >::get(&claim_hash).ok_or(Error::::AttestationNotFound)?; + let attestation = Attestations::::get(&claim_hash).ok_or(Error::::AttestationNotFound)?; ensure!(!attestation.revoked, Error::::AlreadyRevoked); @@ -355,35 +439,11 @@ pub mod pallet { Ok(Some(::WeightInfo::revoke()).into()) } - /// Remove an attestation. - /// - /// The origin must be either the creator of the attestation or an - /// entity which is an ancestor of the attester in the delegation tree, - /// i.e., it was either the delegator of the attester or an ancestor - /// thereof. - /// - /// Emits `AttestationRemoved`. - /// - /// # - /// Weight: O(P) where P is the number of steps required to verify that - /// the dispatch Origin controls the delegation entitled to revoke the - /// attestation. It is bounded by `max_parent_checks`. - /// - Reads: [Origin Account], Attestations, delegation::Roots - /// - Reads per delegation step P: delegation::Delegations - /// - Writes: Attestations, DelegatedAttestations - /// # - #[pallet::weight( - ::WeightInfo::remove() - .saturating_add(authorization.as_ref().map(|ac| ac.can_remove_weight()).unwrap_or(0)) - )] - pub fn remove( - origin: OriginFor, + pub fn remove_attestation( + who: AttesterOf, claim_hash: ClaimHashOf, authorization: Option, ) -> DispatchResultWithPostInfo { - let source = ::EnsureOrigin::ensure_origin(origin)?; - let who = source.subject(); - let attestation = Attestations::::get(&claim_hash).ok_or(Error::::AttestationNotFound)?; if attestation.attester != who { @@ -400,40 +460,12 @@ pub mod pallet { log::debug!("removing Attestation"); - Self::remove_attestation(attestation, claim_hash); + Self::remove_attestation_entry(attestation, claim_hash); Self::deposit_event(Event::AttestationRemoved(who, claim_hash)); Ok(Some(::WeightInfo::remove()).into()) } - /// Reclaim a storage deposit by removing an attestation - /// - /// Emits `DepositReclaimed`. - /// - /// # - /// Weight: O(1) - /// - Reads: [Origin Account], Attestations, DelegatedAttestations - /// - Writes: Attestations, DelegatedAttestations - /// # - #[pallet::weight(::WeightInfo::reclaim_deposit())] - pub fn reclaim_deposit(origin: OriginFor, claim_hash: ClaimHashOf) -> DispatchResult { - let who = ensure_signed(origin)?; - let attestation = Attestations::::get(&claim_hash).ok_or(Error::::AttestationNotFound)?; - - ensure!(attestation.deposit.owner == who, Error::::Unauthorized); - - // *** No Fail beyond this point *** - - log::debug!("removing Attestation"); - - Self::remove_attestation(attestation, claim_hash); - Self::deposit_event(Event::DepositReclaimed(who, claim_hash)); - - Ok(()) - } - } - - impl Pallet { /// Reserve the deposit and record the deposit on chain. /// /// Fails if the `payer` has a balance less than deposit. @@ -449,7 +481,7 @@ pub mod pallet { }) } - fn remove_attestation(attestation: AttestationDetails, claim_hash: ClaimHashOf) { + fn remove_attestation_entry(attestation: AttestationDetails, claim_hash: ClaimHashOf) { kilt_support::free_deposit::, CurrencyOf>(&attestation.deposit); Attestations::::remove(&claim_hash); if let Some(authorization_id) = &attestation.authorization_id { diff --git a/pallets/public-credentials/Cargo.toml b/pallets/public-credentials/Cargo.toml index 674bc63c9e..48d826733b 100644 --- a/pallets/public-credentials/Cargo.toml +++ b/pallets/public-credentials/Cargo.toml @@ -24,7 +24,7 @@ scale-info = {version = "2.1.1", default-features = false, features = ["derive"] serde = {version = "1.0.137", optional = true} # Internal dependencies -# attestation = {default-features = false, path = "../attestation"} +attestation = {default-features = false, path = "../attestation"} ctype = {default-features = false, path = "../ctype"} kilt-support = {default-features = false, path = "../../support"} @@ -57,7 +57,7 @@ runtime-benchmarks = [ ] std = [ "codec/std", - # "attestation/std", + "attestation/std", "ctype/std", "frame-support/std", "frame-system/std", diff --git a/pallets/public-credentials/src/credentials.rs b/pallets/public-credentials/src/credentials.rs index 89098b62be..f95044fbae 100644 --- a/pallets/public-credentials/src/credentials.rs +++ b/pallets/public-credentials/src/credentials.rs @@ -28,10 +28,15 @@ pub struct Claim { pub contents: Content, } +// TODO: Add support for delegation and claimer's signature. #[derive(Encode, Decode, Clone, MaxEncodedLen, RuntimeDebug, PartialEq, Eq, PartialOrd, Ord, TypeInfo)] -pub struct Credential { +pub struct Credential { pub claim: Claim, - pub claimer_signature: Option, - pub nonce: Option, + pub nonce: Nonce, pub claim_hash: ClaimHash, } + +pub struct CredentialEntry { + pub block_number: BlockNumber, + pub deposit: Deposit +} diff --git a/pallets/public-credentials/src/lib.rs b/pallets/public-credentials/src/lib.rs index 1dff59ce9e..001759a1af 100644 --- a/pallets/public-credentials/src/lib.rs +++ b/pallets/public-credentials/src/lib.rs @@ -29,7 +29,7 @@ pub mod credentials; pub mod default_weights; -pub use crate::{credentials::*, pallet::*, default_weights::WeightInfo}; +pub use crate::{credentials::*, default_weights::WeightInfo, pallet::*}; #[frame_support::pallet] pub mod pallet { @@ -37,11 +37,18 @@ pub mod pallet { use codec::MaxEncodedLen; - use frame_support::{pallet_prelude::*, traits::{StorageVersion, IsType, ConstU32}, BoundedVec}; - use kilt_support::{signature::VerifySignature, traits::CallSources}; + use frame_support::{ + pallet_prelude::*, + sp_runtime::traits::Saturating, + traits::{Currency, IsType, ReservableCurrency, StorageVersion}, + BoundedVec, + }; + use frame_system::pallet_prelude::*; + use sp_core::H256; + use attestation::{AttesterOf, ClaimHashOf}; use ctype::CtypeHashOf; - use sp_core::H256; + use kilt_support::traits::CallSources; /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); @@ -49,32 +56,29 @@ pub mod pallet { pub(crate) type AccountIdOf = ::AccountId; // TODO: Replace with an enum that includes KILT DIDs and asset DIDs. pub(crate) type SubjectIdOf = AccountIdOf; - // FIXME: replace with ClaimHashOf from attestation pallet - pub(crate) type ClaimHash = BoundedVec>; + pub(crate) type BalanceOf = <::Currency as Currency>>::Balance; pub type CredentialOf = Credential< CtypeHashOf, SubjectIdOf, BoundedVec::MaxEncodedClaimContentLength>, - ::CredentialSignature, - ClaimHash, + ClaimHashOf, H256, >; #[pallet::config] - pub trait Config: frame_system::Config { + pub trait Config: frame_system::Config + attestation::Config { type CredentialClaimerIdentifier: Parameter + MaxEncodedLen; - type CredentialSignatureVerification: VerifySignature< - SignerId = Self::CredentialClaimerIdentifier, - Payload = ClaimHash, - Signature = Self::CredentialSignature, + #[pallet::constant] + type Deposit: Get>; + type EnsureOrigin: EnsureOrigin< + Success = ::OriginSuccess, + ::Origin, >; - type CredentialSignature: Parameter; type Event: From> + IsType<::Event>; #[pallet::constant] type MaxEncodedClaimContentLength: Get; - // FIXME: replace with AttesterOf from attestation pallet - type OriginSuccess: CallSources, AccountIdOf>; + type OriginSuccess: CallSources, AttesterOf>; type WeightInfo: WeightInfo; } @@ -83,97 +87,144 @@ pub mod pallet { #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); + #[pallet::hooks] + impl Hooks> for Pallet {} + + + #[pallet::storage] + #[pallet::getter(fn get_credential_info)] + pub type Credentials = + StorageDoubleMap<_, Twox64Concat, SubjectIdOf, Blake2_128Concat, ClaimHashOf, CredentialEntry, Deposit, BalanceOf>>>; + + // Reverse map to make sure that the same claim hash cannot be issued to two + // different subjects by issuing it to subject #1, then removing it only from + // the attestation pallet and then issuing it to subject #2. + // This map ensures that at any time a claim hash is only issued to a single + // subject. + #[pallet::storage] + #[pallet::getter(fn attested_claim_hashes)] + pub(crate) type CredentialsUnicityIndex = StorageMap<_, Blake2_128Concat, ClaimHashOf, SubjectIdOf>; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - TestEvent, + CredentialStored { + subject_id: SubjectIdOf, + claim_hash: ClaimHashOf, + block_number: BlockNumberFor, + }, } #[pallet::error] pub enum Error { - TestError, + CredentialIssued, + CredentialNotFound, + UnableToPayFees, } -} -// pub mod assets; - -// #[frame_support::pallet] -// pub mod pallet { -// use codec::MaxEncodedLen; -// use frame_support::{ -// dispatch::DispatchResult, -// pallet_prelude::*, -// traits::{ConstU32, Get, StorageVersion}, -// Blake2_128Concat, Twox64Concat, -// }; -// use frame_system::pallet_prelude::{BlockNumberFor, *}; -// use sp_core::H256; - -// use crate::credentials::{Claim, Credential}; -// use attestation::{Attestations, AttesterOf, ClaimHashOf}; -// use ctype::{CtypeHashOf, Ctypes}; -// use kilt_support::{signature::VerifySignature, traits::CallSources}; - -// #[pallet::config] -// pub trait Config: frame_system::Config + attestation::Config { -// type EnsureOrigin: EnsureOrigin< -// Success = ::OriginSuccess, -// ::Origin, -// >; - // type CredentialSignatureVerification: VerifySignature< - // SignerId = Self::CredentialClaimerIdentifier, - // Payload = ClaimHashOf, - // Signature = Self::CredentialSignature, - // >; - // type CredentialSignature: Parameter; - // type CredentialClaimerIdentifier: Parameter + MaxEncodedLen; -// type Event: From> + IsType<::Event>; - // type OriginSuccess: CallSources, AttesterOf>; -// type WeightInfo: WeightInfo; -// } - -// #[pallet::storage] -// #[pallet::getter(fn get_credential_info)] -// pub type Credentials = -// StorageDoubleMap<_, Twox64Concat, SubjectIdOf, Blake2_128Concat, ClaimHashOf, BlockNumberFor>; - -// #[pallet::hooks] -// impl Hooks> for Pallet {} - -// #[pallet::call] -// impl Pallet { -// #[pallet::weight(0)] -// pub fn add(origin: OriginFor, credential: CredentialOf) -> DispatchResult { -// let source = ::EnsureOrigin::ensure_origin(origin)?; -// let attester = source.subject(); -// let payer = source.sender(); - -// let Credential { -// claim: Claim { -// ctype_hash, -// subject, -// contents, -// }, -// claimer_signature, -// nonce, -// claim_hash, -// } = credential; - -// // Check that a CType exists. -// ensure!( -// Ctypes::::contains_key(&credential.claim.ctype_hash), -// // FIXME -// Error::::Test -// ); - -// // Check that an attestation with the same hash does NOT exist. -// ensure!( -// !Attestations::::contains_key(&credential.claim_hash), -// // FIXME -// Error::::Test -// ); - -// Ok(()) -// } -// } -// } + #[pallet::call] + impl Pallet { + #[pallet::weight(0)] + pub fn add(origin: OriginFor, credential: CredentialOf) -> DispatchResult { + let source = ::EnsureOrigin::ensure_origin(origin)?; + let attester = source.subject(); + let payer = source.sender(); + let deposit_amount = ::Deposit::get(); + + let Credential { + claim: Claim { + ctype_hash, + subject, + contents: _, + }, + claim_hash, + nonce: _, + } = credential; + + // Check that the same attestation has not already been issued previously + // (potentially to a different subject) + ensure!( + !CredentialsUnicityIndex::::contains_key(&claim_hash), + Error::::AttestationCreated + ); + + // Check that enough funds can be reserved to pay for both attestation and + // public info deposits + let attestation_deposit_amount = ::Deposit::get(); + ensure!( + <::Currency as ReservableCurrency>>::can_reserve( + &payer, + deposit_amount.saturating_add(attestation_deposit_amount) + ), + Error::::UnableToPayFees + ); + + // Delegate to the attestation pallet writing the attestation information and + // reserve its part of the deposit + attestation::Pallet::::write_attestation(ctype_hash, claim_hash, attester, payer.clone(), None)?; + + // *** No Fail beyond this point *** + + // Take the rest of the deposit + let deposit = Self::reserve_deposit(payer, deposit_amount) + let block_number = frame_system::Pallet::::block_number(); + + Credentials::::insert(&subject, &claim_hash, CredentialEntry { deposit, block_number }); + CredentialsUnicityIndex::::insert(&claim_hash, subject.clone()); + + Self::deposit_event(Event::CredentialStored { + subject_id: subject, + claim_hash, + block_number, + }); + + Ok(()) + } + + #[pallet::weight(0)] + pub fn revoke(origin: OriginFor, claim_hash: ClaimHashOf) -> DispatchResult { + let source = ::EnsureOrigin::ensure_origin(origin)?; + let attester = source.subject(); + + // Verifies that the credential exists. + ensure!( + CredentialsUnicityIndex::::contains_key(&claim_hash), + Error::::CredentialNotFound + ); + + // Delegate to the attestation pallet the revocation logic. + // This guarantees that the owner is calling this function. + attestation::Pallet::::revoke_attestation(attester, claim_hash, None)?; + + // *** No Fail beyond this point *** + + // Take the rest of the deposit + ::Currency::reserve(&payer, deposit_amount)?; + + let block_number = frame_system::Pallet::::block_number(); + + Credentials::::insert(&subject, &claim_hash, block_number); + CredentialsUnicityIndex::::insert(&claim_hash, subject.clone()); + + Self::deposit_event(Event::CredentialStored { + subject_id: subject, + claim_hash, + block_number, + }); + + Ok(()) + } + + pub(crate) fn reserve_deposit( + payer: AccountIdOf, + deposit: BalanceOf, + ) -> Result, BalanceOf>, DispatchError> { + CurrencyOf::::reserve(&payer, deposit)?; + + Ok(Deposit::, BalanceOf> { + owner: payer, + amount: deposit, + }) + } + } +} From 8704a4dbf0227c30e037fbb8c78ab76437cf9835 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Fri, 10 Jun 2022 16:30:01 +0200 Subject: [PATCH 003/138] wip: FFS --- pallets/attestation/src/lib.rs | 28 +++--- pallets/public-credentials/src/credentials.rs | 17 ++-- pallets/public-credentials/src/lib.rs | 85 +++++++++++++------ runtimes/common/src/lib.rs | 2 +- rust-toolchain.toml | 2 +- 5 files changed, 91 insertions(+), 43 deletions(-) diff --git a/pallets/attestation/src/lib.rs b/pallets/attestation/src/lib.rs index 7924ed047b..0a30622493 100644 --- a/pallets/attestation/src/lib.rs +++ b/pallets/attestation/src/lib.rs @@ -333,18 +333,8 @@ pub mod pallet { #[pallet::weight(::WeightInfo::reclaim_deposit())] pub fn reclaim_deposit(origin: OriginFor, claim_hash: ClaimHashOf) -> DispatchResult { let who = ensure_signed(origin)?; - let attestation = Attestations::::get(&claim_hash).ok_or(Error::::AttestationNotFound)?; - - ensure!(attestation.deposit.owner == who, Error::::Unauthorized); - - // *** No Fail beyond this point *** - - log::debug!("removing Attestation"); - Self::remove_attestation_entry(attestation, claim_hash); - Self::deposit_event(Event::DepositReclaimed(who, claim_hash)); - - Ok(()) + Self::reclaim_dep(who, claim_hash) } } @@ -466,6 +456,22 @@ pub mod pallet { Ok(Some(::WeightInfo::remove()).into()) } + // TODO: Change naming + pub fn reclaim_dep(sender: AccountIdOf, claim_hash: ClaimHashOf) -> DispatchResult { + let attestation = Attestations::::get(&claim_hash).ok_or(Error::::AttestationNotFound)?; + + ensure!(attestation.deposit.owner == sender, Error::::Unauthorized); + + // *** No Fail beyond this point *** + + log::debug!("removing Attestation"); + + Self::remove_attestation_entry(attestation, claim_hash); + Self::deposit_event(Event::DepositReclaimed(sender, claim_hash)); + + Ok(()) + } + /// Reserve the deposit and record the deposit on chain. /// /// Fails if the `payer` has a balance less than deposit. diff --git a/pallets/public-credentials/src/credentials.rs b/pallets/public-credentials/src/credentials.rs index f95044fbae..bd227a9c23 100644 --- a/pallets/public-credentials/src/credentials.rs +++ b/pallets/public-credentials/src/credentials.rs @@ -21,7 +21,11 @@ use scale_info::TypeInfo; use frame_support::RuntimeDebug; -#[derive(Encode, Decode, Clone, MaxEncodedLen, RuntimeDebug, PartialEq, Eq, PartialOrd, Ord, TypeInfo)] +use kilt_support::deposit::Deposit; + +use crate::{AccountIdOf, BalanceOf, Config}; + +#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, PartialOrd, Ord, TypeInfo)] pub struct Claim { pub ctype_hash: CtypeHash, pub subject: SubjectIdentifier, @@ -29,14 +33,17 @@ pub struct Claim { } // TODO: Add support for delegation and claimer's signature. -#[derive(Encode, Decode, Clone, MaxEncodedLen, RuntimeDebug, PartialEq, Eq, PartialOrd, Ord, TypeInfo)] +#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, PartialOrd, Ord, TypeInfo)] pub struct Credential { pub claim: Claim, pub nonce: Nonce, pub claim_hash: ClaimHash, } -pub struct CredentialEntry { - pub block_number: BlockNumber, - pub deposit: Deposit +#[derive(Encode, Decode, Clone, MaxEncodedLen, RuntimeDebug, TypeInfo)] +#[scale_info(skip_type_params(T))] +#[codec(mel_bound())] +pub struct CredentialEntry { + pub block_number: T::BlockNumber, + pub deposit: Deposit, BalanceOf>, } diff --git a/pallets/public-credentials/src/lib.rs b/pallets/public-credentials/src/lib.rs index 001759a1af..7870ac5756 100644 --- a/pallets/public-credentials/src/lib.rs +++ b/pallets/public-credentials/src/lib.rs @@ -48,7 +48,7 @@ pub mod pallet { use attestation::{AttesterOf, ClaimHashOf}; use ctype::CtypeHashOf; - use kilt_support::traits::CallSources; + use kilt_support::{deposit::Deposit, traits::CallSources}; /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); @@ -57,6 +57,7 @@ pub mod pallet { // TODO: Replace with an enum that includes KILT DIDs and asset DIDs. pub(crate) type SubjectIdOf = AccountIdOf; pub(crate) type BalanceOf = <::Currency as Currency>>::Balance; + pub(crate) type CurrencyOf = ::Currency; pub type CredentialOf = Credential< CtypeHashOf, @@ -90,11 +91,16 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet {} - #[pallet::storage] #[pallet::getter(fn get_credential_info)] - pub type Credentials = - StorageDoubleMap<_, Twox64Concat, SubjectIdOf, Blake2_128Concat, ClaimHashOf, CredentialEntry, Deposit, BalanceOf>>>; + pub type Credentials = StorageDoubleMap< + _, + Twox64Concat, + SubjectIdOf, + Blake2_128Concat, + ClaimHashOf, + CredentialEntry, + >; // Reverse map to make sure that the same claim hash cannot be issued to two // different subjects by issuing it to subject #1, then removing it only from @@ -113,6 +119,10 @@ pub mod pallet { claim_hash: ClaimHashOf, block_number: BlockNumberFor, }, + CredentialRemoved { + subject_id: SubjectIdOf, + claim_hash: ClaimHashOf, + }, } #[pallet::error] @@ -145,7 +155,7 @@ pub mod pallet { // (potentially to a different subject) ensure!( !CredentialsUnicityIndex::::contains_key(&claim_hash), - Error::::AttestationCreated + Error::::CredentialIssued ); // Check that enough funds can be reserved to pay for both attestation and @@ -163,10 +173,11 @@ pub mod pallet { // reserve its part of the deposit attestation::Pallet::::write_attestation(ctype_hash, claim_hash, attester, payer.clone(), None)?; + // Take the rest of the deposit + let deposit = Self::reserve_deposit(payer, deposit_amount)?; + // *** No Fail beyond this point *** - // Take the rest of the deposit - let deposit = Self::reserve_deposit(payer, deposit_amount) let block_number = frame_system::Pallet::::block_number(); Credentials::::insert(&subject, &claim_hash, CredentialEntry { deposit, block_number }); @@ -182,39 +193,48 @@ pub mod pallet { } #[pallet::weight(0)] - pub fn revoke(origin: OriginFor, claim_hash: ClaimHashOf) -> DispatchResult { + pub fn remove(origin: OriginFor, claim_hash: ClaimHashOf) -> DispatchResultWithPostInfo { let source = ::EnsureOrigin::ensure_origin(origin)?; let attester = source.subject(); // Verifies that the credential exists. - ensure!( - CredentialsUnicityIndex::::contains_key(&claim_hash), - Error::::CredentialNotFound - ); + let credential_subject = + CredentialsUnicityIndex::::get(&claim_hash).ok_or(Error::::CredentialNotFound)?; + // Should never happen if the line above succeeds. + let credential_entry = + Credentials::::get(&credential_subject, &claim_hash).ok_or(Error::::CredentialNotFound)?; - // Delegate to the attestation pallet the revocation logic. + // Delegate to the attestation pallet the removal logic. // This guarantees that the owner is calling this function. - attestation::Pallet::::revoke_attestation(attester, claim_hash, None)?; + let result = attestation::Pallet::::remove_attestation(attester, claim_hash, None)?; - // *** No Fail beyond this point *** + Self::remove_credential_entry(credential_subject, claim_hash, credential_entry); - // Take the rest of the deposit - ::Currency::reserve(&payer, deposit_amount)?; + Ok(result) + } - let block_number = frame_system::Pallet::::block_number(); + #[pallet::weight(0)] + pub fn reclaim_deposit(origin: OriginFor, claim_hash: ClaimHashOf) -> DispatchResult { + let who = ensure_signed(origin)?; - Credentials::::insert(&subject, &claim_hash, block_number); - CredentialsUnicityIndex::::insert(&claim_hash, subject.clone()); + // Verifies that the credential exists. + let credential_subject = + CredentialsUnicityIndex::::get(&claim_hash).ok_or(Error::::CredentialNotFound)?; + // Should never happen if the line above succeeds. + let credential_entry = + Credentials::::get(&credential_subject, &claim_hash).ok_or(Error::::CredentialNotFound)?; - Self::deposit_event(Event::CredentialStored { - subject_id: subject, - claim_hash, - block_number, - }); + // Delegate to the attestation pallet the removal logic. + // This guarantees that the owner is calling this function. + attestation::Pallet::::reclaim_dep(who, claim_hash)?; + + Self::remove_credential_entry(credential_subject, claim_hash, credential_entry); Ok(()) } + } + impl Pallet { pub(crate) fn reserve_deposit( payer: AccountIdOf, deposit: BalanceOf, @@ -226,5 +246,20 @@ pub mod pallet { amount: deposit, }) } + + pub(crate) fn remove_credential_entry( + credential_subject: SubjectIdOf, + claim_hash: ClaimHashOf, + credential: CredentialEntry, + ) { + kilt_support::free_deposit::, CurrencyOf>(&credential.deposit); + Credentials::::remove(&credential_subject, &claim_hash); + CredentialsUnicityIndex::::remove(&claim_hash); + + Self::deposit_event(Event::CredentialRemoved { + subject_id: credential_subject, + claim_hash, + }); + } } } diff --git a/runtimes/common/src/lib.rs b/runtimes/common/src/lib.rs index f7a1d8119c..70f5cb27e8 100644 --- a/runtimes/common/src/lib.rs +++ b/runtimes/common/src/lib.rs @@ -30,7 +30,7 @@ pub use opaque::*; pub use frame_support::weights::constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; use frame_support::{ parameter_types, - traits::{Contains, ContainsLengthBound, Currency, Get, OnRuntimeUpgrade, SortedMembers}, + traits::{Contains, ContainsLengthBound, Currency, Get, SortedMembers}, weights::DispatchClass, }; use frame_system::limits; diff --git a/rust-toolchain.toml b/rust-toolchain.toml index cde82b5937..2504682459 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2022-05-11" +channel = "stable" targets = [ "wasm32-unknown-unknown" ] From 323296d54573f6d7280b91e074ccf4682c98a741 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Mon, 13 Jun 2022 16:43:52 +0200 Subject: [PATCH 004/138] wip: tests for the new pallet --- Cargo.lock | 14 ++++-------- pallets/public-credentials/Cargo.toml | 9 +------- pallets/public-credentials/src/lib.rs | 31 ++++++++++++++++----------- 3 files changed, 23 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aadba2ca97..8429f4611f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -354,7 +354,7 @@ dependencies = [ "sp-keystore", "sp-runtime", "sp-std", - "substrate-wasm-builder-runner", + "substrate-wasm-builder", ] [[package]] @@ -1427,7 +1427,7 @@ dependencies = [ "sp-keystore", "sp-runtime", "sp-std", - "substrate-wasm-builder-runner", + "substrate-wasm-builder", ] [[package]] @@ -1953,7 +1953,7 @@ dependencies = [ "sp-keystore", "sp-runtime", "sp-std", - "substrate-wasm-builder-runner", + "substrate-wasm-builder", ] [[package]] @@ -2010,7 +2010,7 @@ dependencies = [ "sp-keystore", "sp-runtime", "sp-std", - "substrate-wasm-builder-runner", + "substrate-wasm-builder", ] [[package]] @@ -11333,12 +11333,6 @@ dependencies = [ "wasm-gc-api", ] -[[package]] -name = "substrate-wasm-builder-runner" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "316626afcac0219c95116e6a2518e622484c2814182bd225fbf4da4f67e27e8f" - [[package]] name = "subtle" version = "2.4.1" diff --git a/pallets/public-credentials/Cargo.toml b/pallets/public-credentials/Cargo.toml index 48d826733b..61ae79c392 100644 --- a/pallets/public-credentials/Cargo.toml +++ b/pallets/public-credentials/Cargo.toml @@ -7,7 +7,7 @@ repository = "https://github.com/KILTprotocol/mashnet-node" version = "1.6.2" [dev-dependencies] -# attestation = {features = ["mock"], path = "../attestation"} +attestation = {features = ["mock"], path = "../attestation"} ctype = {features = ["mock"], path = "../ctype"} kilt-support = {features = ["mock"], path = "../../support"} @@ -41,13 +41,6 @@ sp-std = {branch = "polkadot-v0.9.23", default-features = false, git = "https:// [features] default = ["std"] -mock = [ - "pallet-balances", - "serde", - "sp-core", - "sp-io", - "sp-keystore", -] runtime-benchmarks = [ "frame-benchmarking", "frame-support/runtime-benchmarks", diff --git a/pallets/public-credentials/src/lib.rs b/pallets/public-credentials/src/lib.rs index 7870ac5756..5a7173dacc 100644 --- a/pallets/public-credentials/src/lib.rs +++ b/pallets/public-credentials/src/lib.rs @@ -29,6 +29,9 @@ pub mod credentials; pub mod default_weights; +#[cfg(test)] +mod mock; + pub use crate::{credentials::*, default_weights::WeightInfo, pallet::*}; #[frame_support::pallet] @@ -130,6 +133,7 @@ pub mod pallet { CredentialIssued, CredentialNotFound, UnableToPayFees, + InternalError, } #[pallet::call] @@ -139,7 +143,9 @@ pub mod pallet { let source = ::EnsureOrigin::ensure_origin(origin)?; let attester = source.subject(); let payer = source.sender(); + let deposit_amount = ::Deposit::get(); + let attestation_deposit_amount = ::Deposit::get(); let Credential { claim: Claim { @@ -160,7 +166,6 @@ pub mod pallet { // Check that enough funds can be reserved to pay for both attestation and // public info deposits - let attestation_deposit_amount = ::Deposit::get(); ensure!( <::Currency as ReservableCurrency>>::can_reserve( &payer, @@ -173,11 +178,11 @@ pub mod pallet { // reserve its part of the deposit attestation::Pallet::::write_attestation(ctype_hash, claim_hash, attester, payer.clone(), None)?; - // Take the rest of the deposit - let deposit = Self::reserve_deposit(payer, deposit_amount)?; - // *** No Fail beyond this point *** + // Take the rest of the deposit. Should never fail since we made sure that enough funds can be reserved. + let deposit = Self::reserve_deposit(payer, deposit_amount).map_err(|_| Error::::InternalError)?; + let block_number = frame_system::Pallet::::block_number(); Credentials::::insert(&subject, &claim_hash, CredentialEntry { deposit, block_number }); @@ -197,15 +202,15 @@ pub mod pallet { let source = ::EnsureOrigin::ensure_origin(origin)?; let attester = source.subject(); - // Verifies that the credential exists. + // Verify that the credential exists let credential_subject = CredentialsUnicityIndex::::get(&claim_hash).ok_or(Error::::CredentialNotFound)?; - // Should never happen if the line above succeeds. + // Should never happen if the line above succeeds let credential_entry = - Credentials::::get(&credential_subject, &claim_hash).ok_or(Error::::CredentialNotFound)?; + Credentials::::get(&credential_subject, &claim_hash).ok_or(Error::::InternalError)?; - // Delegate to the attestation pallet the removal logic. - // This guarantees that the owner is calling this function. + // Delegate to the attestation pallet the removal logic + // This guarantees that the authorized owner is calling this function let result = attestation::Pallet::::remove_attestation(attester, claim_hash, None)?; Self::remove_credential_entry(credential_subject, claim_hash, credential_entry); @@ -217,15 +222,15 @@ pub mod pallet { pub fn reclaim_deposit(origin: OriginFor, claim_hash: ClaimHashOf) -> DispatchResult { let who = ensure_signed(origin)?; - // Verifies that the credential exists. + // Verify that the credential exists let credential_subject = CredentialsUnicityIndex::::get(&claim_hash).ok_or(Error::::CredentialNotFound)?; - // Should never happen if the line above succeeds. + // Should never happen if the line above succeeds let credential_entry = - Credentials::::get(&credential_subject, &claim_hash).ok_or(Error::::CredentialNotFound)?; + Credentials::::get(&credential_subject, &claim_hash).ok_or(Error::::InternalError)?; // Delegate to the attestation pallet the removal logic. - // This guarantees that the owner is calling this function. + // This guarantees that the authorized owner is calling this function. attestation::Pallet::::reclaim_dep(who, claim_hash)?; Self::remove_credential_entry(credential_subject, claim_hash, credential_entry); From b586fb22d658d74f718892b0f372c380455442e5 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Tue, 14 Jun 2022 15:13:56 +0200 Subject: [PATCH 005/138] test: add unit tests for the new pallet --- pallets/attestation/src/lib.rs | 4 +- pallets/public-credentials/src/credentials.rs | 14 ++++- pallets/public-credentials/src/lib.rs | 55 +++++++++++++------ support/src/deposit.rs | 2 +- 4 files changed, 53 insertions(+), 22 deletions(-) diff --git a/pallets/attestation/src/lib.rs b/pallets/attestation/src/lib.rs index 0a30622493..3694daee92 100644 --- a/pallets/attestation/src/lib.rs +++ b/pallets/attestation/src/lib.rs @@ -113,11 +113,11 @@ pub mod pallet { /// Type of an attester identifier. pub type AttesterOf = ::AttesterId; + pub type AccountIdOf = ::AccountId; + /// Authorization id type pub(crate) type AuthorizationIdOf = ::AuthorizationId; - pub(crate) type AccountIdOf = ::AccountId; - pub(crate) type BalanceOf = <::Currency as Currency>>::Balance; pub(crate) type CurrencyOf = ::Currency; diff --git a/pallets/public-credentials/src/credentials.rs b/pallets/public-credentials/src/credentials.rs index bd227a9c23..5f95765225 100644 --- a/pallets/public-credentials/src/credentials.rs +++ b/pallets/public-credentials/src/credentials.rs @@ -32,15 +32,23 @@ pub struct Claim { pub contents: Content, } -// TODO: Add support for delegation and claimer's signature. #[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, PartialOrd, Ord, TypeInfo)] -pub struct Credential { +pub struct ClaimerSignatureInfo { + pub claimer_id: ClaimerIdentifier, + pub signature_payload: Signature, +} + +// TODO: Add support for delegation. +#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, PartialOrd, Ord, TypeInfo)] +pub struct Credential +{ pub claim: Claim, pub nonce: Nonce, pub claim_hash: ClaimHash, + pub claimer_signature: Option>, } -#[derive(Encode, Decode, Clone, MaxEncodedLen, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, Clone, MaxEncodedLen, RuntimeDebug, PartialEq, Eq, PartialOrd, Ord, TypeInfo)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] pub struct CredentialEntry { diff --git a/pallets/public-credentials/src/lib.rs b/pallets/public-credentials/src/lib.rs index 5a7173dacc..88d077556f 100644 --- a/pallets/public-credentials/src/lib.rs +++ b/pallets/public-credentials/src/lib.rs @@ -31,6 +31,8 @@ pub mod default_weights; #[cfg(test)] mod mock; +#[cfg(test)] +mod tests; pub use crate::{credentials::*, default_weights::WeightInfo, pallet::*}; @@ -38,27 +40,29 @@ pub use crate::{credentials::*, default_weights::WeightInfo, pallet::*}; pub mod pallet { use super::*; - use codec::MaxEncodedLen; - use frame_support::{ pallet_prelude::*, sp_runtime::traits::Saturating, traits::{Currency, IsType, ReservableCurrency, StorageVersion}, - BoundedVec, + BoundedVec, Parameter, }; use frame_system::pallet_prelude::*; use sp_core::H256; use attestation::{AttesterOf, ClaimHashOf}; use ctype::CtypeHashOf; - use kilt_support::{deposit::Deposit, traits::CallSources}; + use kilt_support::{ + deposit::Deposit, + signature::{SignatureVerificationError, VerifySignature}, + traits::CallSources, + }; /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); - pub(crate) type AccountIdOf = ::AccountId; + pub(crate) type AccountIdOf = attestation::AccountIdOf; // TODO: Replace with an enum that includes KILT DIDs and asset DIDs. - pub(crate) type SubjectIdOf = AccountIdOf; + pub(crate) type SubjectIdOf = ::SubjectId; pub(crate) type BalanceOf = <::Currency as Currency>>::Balance; pub(crate) type CurrencyOf = ::Currency; @@ -68,11 +72,19 @@ pub mod pallet { BoundedVec::MaxEncodedClaimContentLength>, ClaimHashOf, H256, + ::ClaimerIdentifier, + ::ClaimerSignature, >; #[pallet::config] - pub trait Config: frame_system::Config + attestation::Config { - type CredentialClaimerIdentifier: Parameter + MaxEncodedLen; + pub trait Config: frame_system::Config + ctype::Config + attestation::Config { + type ClaimerIdentifier: Parameter; + type ClaimerSignature: Parameter; + type ClaimerSignatureVerification: VerifySignature< + SignerId = Self::ClaimerIdentifier, + Payload = Vec, + Signature = Self::ClaimerSignature, + >; #[pallet::constant] type Deposit: Get>; type EnsureOrigin: EnsureOrigin< @@ -83,6 +95,7 @@ pub mod pallet { #[pallet::constant] type MaxEncodedClaimContentLength: Get; type OriginSuccess: CallSources, AttesterOf>; + type SubjectId: Parameter + MaxEncodedLen; type WeightInfo: WeightInfo; } @@ -96,14 +109,8 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn get_credential_info)] - pub type Credentials = StorageDoubleMap< - _, - Twox64Concat, - SubjectIdOf, - Blake2_128Concat, - ClaimHashOf, - CredentialEntry, - >; + pub type Credentials = + StorageDoubleMap<_, Twox64Concat, SubjectIdOf, Blake2_128Concat, ClaimHashOf, CredentialEntry>; // Reverse map to make sure that the same claim hash cannot be issued to two // different subjects by issuing it to subject #1, then removing it only from @@ -133,6 +140,8 @@ pub mod pallet { CredentialIssued, CredentialNotFound, UnableToPayFees, + ClaimerInfoNotFound, + InvalidClaimerSignature, InternalError, } @@ -155,6 +164,7 @@ pub mod pallet { }, claim_hash, nonce: _, + claimer_signature, } = credential; // Check that the same attestation has not already been issued previously @@ -174,6 +184,19 @@ pub mod pallet { Error::::UnableToPayFees ); + // Check the validity of the claimer's signature, if present. + if let Some(ClaimerSignatureInfo { + claimer_id, + signature_payload, + }) = claimer_signature + { + T::ClaimerSignatureVerification::verify(&claimer_id, &claim_hash.encode(), &signature_payload) + .map_err(|err| match err { + SignatureVerificationError::SignerInformationNotPresent => Error::::ClaimerInfoNotFound, + SignatureVerificationError::SignatureInvalid => Error::::InvalidClaimerSignature, + })?; + } + // Delegate to the attestation pallet writing the attestation information and // reserve its part of the deposit attestation::Pallet::::write_attestation(ctype_hash, claim_hash, attester, payer.clone(), None)?; diff --git a/support/src/deposit.rs b/support/src/deposit.rs index a36a96e203..3f8cd03470 100644 --- a/support/src/deposit.rs +++ b/support/src/deposit.rs @@ -19,7 +19,7 @@ use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; /// An amount of balance reserved by the specified address. -#[derive(Clone, Debug, Encode, Decode, PartialEq, TypeInfo, MaxEncodedLen)] +#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, TypeInfo, MaxEncodedLen)] pub struct Deposit { pub owner: Account, pub amount: Balance, From d01497b4becaa7a3cbef8757fe6728bb8dc791f9 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Tue, 14 Jun 2022 15:14:52 +0200 Subject: [PATCH 006/138] chore: fmt --- pallets/public-credentials/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pallets/public-credentials/src/lib.rs b/pallets/public-credentials/src/lib.rs index 88d077556f..d096155909 100644 --- a/pallets/public-credentials/src/lib.rs +++ b/pallets/public-credentials/src/lib.rs @@ -203,7 +203,8 @@ pub mod pallet { // *** No Fail beyond this point *** - // Take the rest of the deposit. Should never fail since we made sure that enough funds can be reserved. + // Take the rest of the deposit. Should never fail since we made sure that + // enough funds can be reserved. let deposit = Self::reserve_deposit(payer, deposit_amount).map_err(|_| Error::::InternalError)?; let block_number = frame_system::Pallet::::block_number(); From e2d56a2bdc81f95d768bcd15b2f3e8628b9a6981 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Tue, 14 Jun 2022 15:21:50 +0200 Subject: [PATCH 007/138] fix: restore toolchain to nightly --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 2504682459..cde82b5937 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "stable" +channel = "nightly-2022-05-11" targets = [ "wasm32-unknown-unknown" ] From 7bc2e70212948d92ba7d3b9b7f0a5ae01fb61287 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Tue, 14 Jun 2022 15:22:00 +0200 Subject: [PATCH 008/138] core: clippy --- nodes/parachain/src/chain_spec.rs | 2 +- pallets/attestation/src/attestations.rs | 2 +- pallets/delegation/src/delegation_hierarchy.rs | 2 +- pallets/did/src/did_details.rs | 8 ++++---- pallets/parachain-staking/src/types.rs | 2 +- runtimes/common/src/constants.rs | 12 ++++++------ runtimes/standalone/src/lib.rs | 6 +++--- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/nodes/parachain/src/chain_spec.rs b/nodes/parachain/src/chain_spec.rs index ddb6cde9b6..c7dfd9d815 100644 --- a/nodes/parachain/src/chain_spec.rs +++ b/nodes/parachain/src/chain_spec.rs @@ -41,7 +41,7 @@ pub fn get_from_seed(seed: &str) -> ::Pu } /// The extensions for the [`ChainSpec`]. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ChainSpecGroup, ChainSpecExtension)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ChainSpecGroup, ChainSpecExtension)] #[serde(deny_unknown_fields)] pub struct Extensions { /// The relay chain of the Parachain. diff --git a/pallets/attestation/src/attestations.rs b/pallets/attestation/src/attestations.rs index 677d10377f..a3adfc8194 100644 --- a/pallets/attestation/src/attestations.rs +++ b/pallets/attestation/src/attestations.rs @@ -24,7 +24,7 @@ use scale_info::TypeInfo; use crate::{AccountIdOf, AttesterOf, AuthorizationIdOf, BalanceOf, Config}; /// An on-chain attestation written by an attester. -#[derive(Clone, Debug, Encode, Decode, PartialEq, TypeInfo, MaxEncodedLen)] +#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] pub struct AttestationDetails { diff --git a/pallets/delegation/src/delegation_hierarchy.rs b/pallets/delegation/src/delegation_hierarchy.rs index 6510bee253..c6ee8c3c76 100644 --- a/pallets/delegation/src/delegation_hierarchy.rs +++ b/pallets/delegation/src/delegation_hierarchy.rs @@ -129,7 +129,7 @@ impl DelegationNode { } /// Delegation information attached to delegation nodes. -#[derive(Clone, Debug, Encode, Decode, PartialEq, TypeInfo, MaxEncodedLen)] +#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] pub struct DelegationDetails { diff --git a/pallets/did/src/did_details.rs b/pallets/did/src/did_details.rs index d8dd578a76..5e8d48e00a 100644 --- a/pallets/did/src/did_details.rs +++ b/pallets/did/src/did_details.rs @@ -242,7 +242,7 @@ pub struct DidPublicKeyDetails { } /// The details associated to a DID identity. -#[derive(Clone, Decode, Encode, PartialEq, TypeInfo, MaxEncodedLen)] +#[derive(Clone, Decode, Encode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] @@ -553,7 +553,7 @@ pub(crate) type DidPublicKeyMap = BoundedBTreeMap, DidPublicKeyDetails, ::MaxPublicKeysPerDid>; /// The details of a new DID to create. -#[derive(Clone, RuntimeDebug, Decode, Encode, PartialEq, TypeInfo)] +#[derive(Clone, RuntimeDebug, Decode, Encode, Eq, PartialEq, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct DidCreationDetails { @@ -573,7 +573,7 @@ pub struct DidCreationDetails { /// Errors that might occur while deriving the authorization verification key /// relationship. -#[derive(Clone, RuntimeDebug, Decode, Encode, PartialEq)] +#[derive(Clone, RuntimeDebug, Decode, Encode, PartialEq, Eq)] pub enum RelationshipDeriveError { /// The call is not callable by a did origin. NotCallableByDid, @@ -604,7 +604,7 @@ pub trait DeriveDidCallAuthorizationVerificationKeyRelationship { /// A DID operation that wraps other extrinsic calls, allowing those /// extrinsic to have a DID origin and perform DID-based authorization upon /// their invocation. -#[derive(Clone, RuntimeDebug, Decode, Encode, PartialEq, TypeInfo)] +#[derive(Clone, RuntimeDebug, Decode, Encode, PartialEq, Eq, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct DidAuthorizedCallOperation { diff --git a/pallets/parachain-staking/src/types.rs b/pallets/parachain-staking/src/types.rs index a253396d12..6b410f43d8 100644 --- a/pallets/parachain-staking/src/types.rs +++ b/pallets/parachain-staking/src/types.rs @@ -363,7 +363,7 @@ pub struct TotalStake { /// The number of delegations a delegator has done within the last session in /// which they delegated. -#[derive(Default, Clone, Encode, Decode, RuntimeDebug, PartialEq, TypeInfo, MaxEncodedLen)] +#[derive(Default, Clone, Encode, Decode, RuntimeDebug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] pub struct DelegationCounter { /// The index of the last delegation. pub round: SessionIndex, diff --git a/runtimes/common/src/constants.rs b/runtimes/common/src/constants.rs index 06b4b38415..8513eae06a 100644 --- a/runtimes/common/src/constants.rs +++ b/runtimes/common/src/constants.rs @@ -201,17 +201,17 @@ pub mod staking { /// We only allow one delegation per round. pub const MaxDelegationsPerRound: u32 = 1; /// Maximum 25 delegators per collator at launch, might be increased later - #[derive(Debug, PartialEq)] + #[derive(Debug, PartialEq, Eq)] pub const MaxDelegatorsPerCollator: u32 = MAX_DELEGATORS_PER_COLLATOR; /// Maximum 1 collator per delegator at launch, will be increased later - #[derive(Debug, PartialEq)] + #[derive(Debug, PartialEq, Eq)] pub const MaxCollatorsPerDelegator: u32 = 1; /// Minimum stake required to be reserved to be a collator is 10_000 pub const MinCollatorStake: Balance = 10_000 * KILT; /// Minimum stake required to be reserved to be a delegator is 1000 pub const MinDelegatorStake: Balance = MIN_DELEGATOR_STAKE; /// Maximum number of collator candidates - #[derive(Debug, PartialEq)] + #[derive(Debug, PartialEq, Eq)] pub const MaxCollatorCandidates: u32 = MAX_CANDIDATES; /// Maximum number of concurrent requests to unlock unstaked balance pub const MaxUnstakeRequests: u32 = 10; @@ -323,12 +323,12 @@ pub mod did { parameter_types! { pub const MaxNewKeyAgreementKeys: u32 = MAX_KEY_AGREEMENT_KEYS; - #[derive(Debug, Clone, PartialEq)] + #[derive(Debug, Clone, PartialEq, Eq)] pub const MaxUrlLength: u32 = MAX_URL_LENGTH; pub const MaxPublicKeysPerDid: u32 = MAX_PUBLIC_KEYS_PER_DID; - #[derive(Debug, Clone, PartialEq)] + #[derive(Debug, Clone, PartialEq, Eq)] pub const MaxTotalKeyAgreementKeys: u32 = MAX_TOTAL_KEY_AGREEMENT_KEYS; - #[derive(Debug, Clone, PartialEq)] + #[derive(Debug, Clone, PartialEq, Eq)] pub const MaxEndpointUrlsCount: u32 = MAX_ENDPOINT_URLS_COUNT; // Standalone block time is half the duration of a parachain block. pub const MaxBlocksTxValidity: BlockNumber = MAX_BLOCKS_TX_VALIDITY; diff --git a/runtimes/standalone/src/lib.rs b/runtimes/standalone/src/lib.rs index d67cb2d81b..e0b4ecb037 100644 --- a/runtimes/standalone/src/lib.rs +++ b/runtimes/standalone/src/lib.rs @@ -362,12 +362,12 @@ impl ctype::Config for Runtime { parameter_types! { pub const MaxNewKeyAgreementKeys: u32 = constants::did::MAX_KEY_AGREEMENT_KEYS; - #[derive(Debug, Clone, PartialEq)] + #[derive(Debug, Clone, PartialEq, Eq)] pub const MaxUrlLength: u32 = constants::did::MAX_URL_LENGTH; pub const MaxPublicKeysPerDid: u32 = constants::did::MAX_PUBLIC_KEYS_PER_DID; - #[derive(Debug, Clone, PartialEq)] + #[derive(Debug, Clone, PartialEq, Eq)] pub const MaxTotalKeyAgreementKeys: u32 = constants::did::MAX_TOTAL_KEY_AGREEMENT_KEYS; - #[derive(Debug, Clone, PartialEq)] + #[derive(Debug, Clone, PartialEq, Eq)] pub const MaxEndpointUrlsCount: u32 = constants::did::MAX_ENDPOINT_URLS_COUNT; // Standalone block time is half the duration of a parachain block. pub const MaxBlocksTxValidity: BlockNumber = constants::did::MAX_BLOCKS_TX_VALIDITY * 2; From f98c1e70b8b6a863609c44ff96b901fa7f99face Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Tue, 14 Jun 2022 17:19:51 +0200 Subject: [PATCH 009/138] wip: minor chores --- Cargo.lock | 1 + pallets/attestation/src/lib.rs | 20 +----- pallets/attestation/src/mock.rs | 4 +- pallets/public-credentials/src/credentials.rs | 13 ++-- pallets/public-credentials/src/lib.rs | 67 +++++++++---------- support/Cargo.toml | 2 + support/src/deposit.rs | 21 ++++++ support/src/lib.rs | 14 +--- 8 files changed, 68 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8429f4611f..593cf24be9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3612,6 +3612,7 @@ version = "1.6.2" dependencies = [ "frame-support", "frame-system", + "pallet-balances", "parity-scale-codec", "scale-info", "sp-core", diff --git a/pallets/attestation/src/lib.rs b/pallets/attestation/src/lib.rs index 3694daee92..1cd701414e 100644 --- a/pallets/attestation/src/lib.rs +++ b/pallets/attestation/src/lib.rs @@ -99,10 +99,9 @@ pub mod pallet { traits::{Currency, Get, ReservableCurrency, StorageVersion}, }; use frame_system::pallet_prelude::*; - use sp_runtime::DispatchError; use ctype::CtypeHashOf; - use kilt_support::{deposit::Deposit, traits::CallSources}; + use kilt_support::traits::CallSources; /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); @@ -364,7 +363,7 @@ pub mod pallet { .transpose()?; let authorization_id = authorization.as_ref().map(|ac| ac.authorization_id()); - let deposit = Pallet::::reserve_deposit(payer, deposit_amount)?; + let deposit = kilt_support::reserve_deposit::, CurrencyOf>(payer, deposit_amount)?; // *** No Fail beyond this point *** @@ -472,21 +471,6 @@ pub mod pallet { Ok(()) } - /// Reserve the deposit and record the deposit on chain. - /// - /// Fails if the `payer` has a balance less than deposit. - pub(crate) fn reserve_deposit( - payer: AccountIdOf, - deposit: BalanceOf, - ) -> Result, BalanceOf>, DispatchError> { - CurrencyOf::::reserve(&payer, deposit)?; - - Ok(Deposit::, BalanceOf> { - owner: payer, - amount: deposit, - }) - } - fn remove_attestation_entry(attestation: AttestationDetails, claim_hash: ClaimHashOf) { kilt_support::free_deposit::, CurrencyOf>(&attestation.deposit); Attestations::::remove(&claim_hash); diff --git a/pallets/attestation/src/mock.rs b/pallets/attestation/src/mock.rs index 15d8f8aaf2..797fa05632 100644 --- a/pallets/attestation/src/mock.rs +++ b/pallets/attestation/src/mock.rs @@ -34,7 +34,7 @@ use kilt_support::deposit::Deposit; use crate::{ pallet::AuthorizationIdOf, AccountIdOf, AttestationAccessControl, AttestationDetails, AttesterOf, BalanceOf, - ClaimHashOf, Config, + ClaimHashOf, Config, CurrencyOf, }; #[cfg(test)] @@ -141,7 +141,7 @@ where } pub fn insert_attestation(claim_hash: ClaimHashOf, details: AttestationDetails) { - crate::Pallet::::reserve_deposit(details.deposit.owner.clone(), details.deposit.amount) + kilt_support::reserve_deposit::, CurrencyOf>(details.deposit.owner.clone(), details.deposit.amount) .expect("Should have balance"); crate::Attestations::::insert(&claim_hash, details.clone()); diff --git a/pallets/public-credentials/src/credentials.rs b/pallets/public-credentials/src/credentials.rs index 5f95765225..72aacf78c8 100644 --- a/pallets/public-credentials/src/credentials.rs +++ b/pallets/public-credentials/src/credentials.rs @@ -23,7 +23,7 @@ use frame_support::RuntimeDebug; use kilt_support::deposit::Deposit; -use crate::{AccountIdOf, BalanceOf, Config}; +use crate::{AccountIdOf, BalanceOf, BlockNumberOf, Config}; #[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, PartialOrd, Ord, TypeInfo)] pub struct Claim { @@ -38,20 +38,19 @@ pub struct ClaimerSignatureInfo { pub signature_payload: Signature, } -// TODO: Add support for delegation. #[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, PartialOrd, Ord, TypeInfo)] -pub struct Credential -{ +pub struct Credential { pub claim: Claim, pub nonce: Nonce, pub claim_hash: ClaimHash, - pub claimer_signature: Option>, + pub claimer_signature: Option, + pub authorization_info: Option, } #[derive(Encode, Decode, Clone, MaxEncodedLen, RuntimeDebug, PartialEq, Eq, PartialOrd, Ord, TypeInfo)] #[scale_info(skip_type_params(T))] #[codec(mel_bound())] -pub struct CredentialEntry { - pub block_number: T::BlockNumber, +pub struct CredentialEntryOf { + pub block_number: BlockNumberOf, pub deposit: Deposit, BalanceOf>, } diff --git a/pallets/public-credentials/src/lib.rs b/pallets/public-credentials/src/lib.rs index d096155909..78d735007f 100644 --- a/pallets/public-credentials/src/lib.rs +++ b/pallets/public-credentials/src/lib.rs @@ -44,7 +44,7 @@ pub mod pallet { pallet_prelude::*, sp_runtime::traits::Saturating, traits::{Currency, IsType, ReservableCurrency, StorageVersion}, - BoundedVec, Parameter, + Parameter, }; use frame_system::pallet_prelude::*; use sp_core::H256; @@ -52,7 +52,6 @@ pub mod pallet { use attestation::{AttesterOf, ClaimHashOf}; use ctype::CtypeHashOf; use kilt_support::{ - deposit::Deposit, signature::{SignatureVerificationError, VerifySignature}, traits::CallSources, }; @@ -61,19 +60,23 @@ pub mod pallet { const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); pub(crate) type AccountIdOf = attestation::AccountIdOf; - // TODO: Replace with an enum that includes KILT DIDs and asset DIDs. - pub(crate) type SubjectIdOf = ::SubjectId; pub(crate) type BalanceOf = <::Currency as Currency>>::Balance; + pub(crate) type BlockNumberOf = ::BlockNumber; + // No easy way to check whether the two currencies are the same and check for `can_withdraw` conditions. + // Maybe with #[transactional] we could stop caring and simply rollback if the two are the same and there is not enough + // for both operations. pub(crate) type CurrencyOf = ::Currency; + // TODO: Replace with an enum that includes KILT DIDs and asset DIDs. + pub(crate) type SubjectIdOf = ::SubjectId; pub type CredentialOf = Credential< CtypeHashOf, SubjectIdOf, - BoundedVec::MaxEncodedClaimContentLength>, + Vec, ClaimHashOf, H256, - ::ClaimerIdentifier, - ::ClaimerSignature, + ClaimerSignatureInfo<::ClaimerIdentifier, ::ClaimerSignature>, + ::AccessControl >; #[pallet::config] @@ -95,7 +98,7 @@ pub mod pallet { #[pallet::constant] type MaxEncodedClaimContentLength: Get; type OriginSuccess: CallSources, AttesterOf>; - type SubjectId: Parameter + MaxEncodedLen; + type SubjectId: Parameter + MaxEncodedLen + TryFrom>; type WeightInfo: WeightInfo; } @@ -110,13 +113,14 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn get_credential_info)] pub type Credentials = - StorageDoubleMap<_, Twox64Concat, SubjectIdOf, Blake2_128Concat, ClaimHashOf, CredentialEntry>; + StorageDoubleMap<_, Twox64Concat, SubjectIdOf, Blake2_128Concat, ClaimHashOf, CredentialEntryOf>; // Reverse map to make sure that the same claim hash cannot be issued to two // different subjects by issuing it to subject #1, then removing it only from // the attestation pallet and then issuing it to subject #2. - // This map ensures that at any time a claim hash is only issued to a single + // This map ensures that at any time a claim hash is only linked (i.e., issued) to a single // subject. + // Not exposed to the outside world. #[pallet::storage] #[pallet::getter(fn attested_claim_hashes)] pub(crate) type CredentialsUnicityIndex = StorageMap<_, Blake2_128Concat, ClaimHashOf, SubjectIdOf>; @@ -137,7 +141,7 @@ pub mod pallet { #[pallet::error] pub enum Error { - CredentialIssued, + CredentialAlreadyIssued, CredentialNotFound, UnableToPayFees, ClaimerInfoNotFound, @@ -147,8 +151,9 @@ pub mod pallet { #[pallet::call] impl Pallet { + #[allow(clippy::boxed_local)] #[pallet::weight(0)] - pub fn add(origin: OriginFor, credential: CredentialOf) -> DispatchResult { + pub fn add(origin: OriginFor, credential: Box>) -> DispatchResult { let source = ::EnsureOrigin::ensure_origin(origin)?; let attester = source.subject(); let payer = source.sender(); @@ -165,19 +170,23 @@ pub mod pallet { claim_hash, nonce: _, claimer_signature, - } = credential; + authorization_info, + } = *credential; // Check that the same attestation has not already been issued previously // (potentially to a different subject) ensure!( !CredentialsUnicityIndex::::contains_key(&claim_hash), - Error::::CredentialIssued + Error::::CredentialAlreadyIssued ); // Check that enough funds can be reserved to pay for both attestation and - // public info deposits + // public info deposits. + // It is harder to use two potentially different currencies while making sure that, if the same, the sum can be reserved, + // but if they are not, then each deposit could be reserved separately. + // We could switch to using `NamedReservableCurrency` to do that. ensure!( - <::Currency as ReservableCurrency>>::can_reserve( + as ReservableCurrency>>::can_reserve( &payer, deposit_amount.saturating_add(attestation_deposit_amount) ), @@ -199,17 +208,17 @@ pub mod pallet { // Delegate to the attestation pallet writing the attestation information and // reserve its part of the deposit - attestation::Pallet::::write_attestation(ctype_hash, claim_hash, attester, payer.clone(), None)?; + attestation::Pallet::::write_attestation(ctype_hash, claim_hash, attester, payer.clone(), authorization_info)?; // *** No Fail beyond this point *** - // Take the rest of the deposit. Should never fail since we made sure that + // Take the rest of the deposit. Should never fail since we made sure above that // enough funds can be reserved. - let deposit = Self::reserve_deposit(payer, deposit_amount).map_err(|_| Error::::InternalError)?; + let deposit = kilt_support::reserve_deposit::, CurrencyOf>(payer, deposit_amount).map_err(|_| Error::::InternalError)?; let block_number = frame_system::Pallet::::block_number(); - Credentials::::insert(&subject, &claim_hash, CredentialEntry { deposit, block_number }); + Credentials::::insert(&subject, &claim_hash, CredentialEntryOf { deposit, block_number }); CredentialsUnicityIndex::::insert(&claim_hash, subject.clone()); Self::deposit_event(Event::CredentialStored { @@ -222,7 +231,7 @@ pub mod pallet { } #[pallet::weight(0)] - pub fn remove(origin: OriginFor, claim_hash: ClaimHashOf) -> DispatchResultWithPostInfo { + pub fn remove(origin: OriginFor, claim_hash: ClaimHashOf, authorization: Option) -> DispatchResultWithPostInfo { let source = ::EnsureOrigin::ensure_origin(origin)?; let attester = source.subject(); @@ -235,7 +244,7 @@ pub mod pallet { // Delegate to the attestation pallet the removal logic // This guarantees that the authorized owner is calling this function - let result = attestation::Pallet::::remove_attestation(attester, claim_hash, None)?; + let result = attestation::Pallet::::remove_attestation(attester, claim_hash, authorization)?; Self::remove_credential_entry(credential_subject, claim_hash, credential_entry); @@ -264,22 +273,10 @@ pub mod pallet { } impl Pallet { - pub(crate) fn reserve_deposit( - payer: AccountIdOf, - deposit: BalanceOf, - ) -> Result, BalanceOf>, DispatchError> { - CurrencyOf::::reserve(&payer, deposit)?; - - Ok(Deposit::, BalanceOf> { - owner: payer, - amount: deposit, - }) - } - pub(crate) fn remove_credential_entry( credential_subject: SubjectIdOf, claim_hash: ClaimHashOf, - credential: CredentialEntry, + credential: CredentialEntryOf, ) { kilt_support::free_deposit::, CurrencyOf>(&credential.deposit); Credentials::::remove(&credential_subject, &claim_hash); diff --git a/support/Cargo.toml b/support/Cargo.toml index 51f4acba6a..ae34b6bb82 100644 --- a/support/Cargo.toml +++ b/support/Cargo.toml @@ -12,6 +12,7 @@ scale-info = {version = "2.1.1", default-features = false, features = ["derive"] frame-support = {branch = "polkadot-v0.9.23", default-features = false, git = "https://github.com/paritytech/substrate"} frame-system = {branch = "polkadot-v0.9.23", default-features = false, git = "https://github.com/paritytech/substrate"} +pallet-balances = {branch = "polkadot-v0.9.23", default-features = false, git = "https://github.com/paritytech/substrate"} sp-core = {branch = "polkadot-v0.9.23", default-features = false, git = "https://github.com/paritytech/substrate"} sp-runtime = {branch = "polkadot-v0.9.23", default-features = false, git = "https://github.com/paritytech/substrate"} sp-std = {branch = "polkadot-v0.9.23", default-features = false, git = "https://github.com/paritytech/substrate"} @@ -31,6 +32,7 @@ std = [ "codec/std", "frame-support/std", "frame-system/std", + "pallet-balances/std", "scale-info/std", "sp-core/std", "sp-runtime/std", diff --git a/support/src/deposit.rs b/support/src/deposit.rs index 3f8cd03470..6479bf3051 100644 --- a/support/src/deposit.rs +++ b/support/src/deposit.rs @@ -16,7 +16,9 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::traits::ReservableCurrency; use scale_info::TypeInfo; +use sp_runtime::{DispatchError, traits::Zero}; /// An amount of balance reserved by the specified address. #[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, TypeInfo, MaxEncodedLen)] @@ -24,3 +26,22 @@ pub struct Deposit { pub owner: Account, pub amount: Balance, } + +pub fn reserve_deposit>( + account: Account, + deposit_amount: Currency::Balance, +) -> Result< + Deposit, + DispatchError> +{ + Currency::reserve(&account, deposit_amount)?; + Ok(Deposit { + owner: account, + amount: deposit_amount, + }) +} + +pub fn free_deposit>(deposit: &Deposit) { + let err_amount = Currency::unreserve(&deposit.owner, deposit.amount); + debug_assert!(err_amount.is_zero()); +} diff --git a/support/src/lib.rs b/support/src/lib.rs index 7a58831778..37582a346b 100644 --- a/support/src/lib.rs +++ b/support/src/lib.rs @@ -17,20 +17,10 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org #![cfg_attr(not(feature = "std"), no_std)] -use deposit::Deposit; -use frame_support::traits::{Currency, ReservableCurrency}; -use sp_runtime::traits::Zero; - pub mod deposit; +pub use deposit::{free_deposit, reserve_deposit}; + #[cfg(any(feature = "runtime-benchmarks", feature = "mock"))] pub mod mock; pub mod signature; pub mod traits; - -pub fn free_deposit(deposit: &Deposit) -where - C: Currency + ReservableCurrency, -{ - let err_amount = C::unreserve(&deposit.owner, deposit.amount); - debug_assert!(err_amount.is_zero()); -} From 21aeb3a4af13c8a59d6d410ad1ddaa62abca829b Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Wed, 15 Jun 2022 09:25:31 +0200 Subject: [PATCH 010/138] test: add hcecks for deposit reservations --- pallets/attestation/src/lib.rs | 5 ++--- pallets/public-credentials/src/lib.rs | 5 +++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pallets/attestation/src/lib.rs b/pallets/attestation/src/lib.rs index 1cd701414e..761723fb02 100644 --- a/pallets/attestation/src/lib.rs +++ b/pallets/attestation/src/lib.rs @@ -333,7 +333,7 @@ pub mod pallet { pub fn reclaim_deposit(origin: OriginFor, claim_hash: ClaimHashOf) -> DispatchResult { let who = ensure_signed(origin)?; - Self::reclaim_dep(who, claim_hash) + Self::unlock_deposit(who, claim_hash) } } @@ -455,8 +455,7 @@ pub mod pallet { Ok(Some(::WeightInfo::remove()).into()) } - // TODO: Change naming - pub fn reclaim_dep(sender: AccountIdOf, claim_hash: ClaimHashOf) -> DispatchResult { + pub fn unlock_deposit(sender: AccountIdOf, claim_hash: ClaimHashOf) -> DispatchResult { let attestation = Attestations::::get(&claim_hash).ok_or(Error::::AttestationNotFound)?; ensure!(attestation.deposit.owner == sender, Error::::Unauthorized); diff --git a/pallets/public-credentials/src/lib.rs b/pallets/public-credentials/src/lib.rs index 78d735007f..07da443d6b 100644 --- a/pallets/public-credentials/src/lib.rs +++ b/pallets/public-credentials/src/lib.rs @@ -248,6 +248,7 @@ pub mod pallet { Self::remove_credential_entry(credential_subject, claim_hash, credential_entry); + // TODO: return the actual fee used. Ok(result) } @@ -264,7 +265,7 @@ pub mod pallet { // Delegate to the attestation pallet the removal logic. // This guarantees that the authorized owner is calling this function. - attestation::Pallet::::reclaim_dep(who, claim_hash)?; + attestation::Pallet::::unlock_deposit(who, claim_hash)?; Self::remove_credential_entry(credential_subject, claim_hash, credential_entry); @@ -273,7 +274,7 @@ pub mod pallet { } impl Pallet { - pub(crate) fn remove_credential_entry( + fn remove_credential_entry( credential_subject: SubjectIdOf, claim_hash: ClaimHashOf, credential: CredentialEntryOf, From bb4d4218218fa4b29f42115d60c5603cf0bc1148 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Wed, 15 Jun 2022 11:30:29 +0200 Subject: [PATCH 011/138] test: add InvalidInput test case --- pallets/public-credentials/src/lib.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pallets/public-credentials/src/lib.rs b/pallets/public-credentials/src/lib.rs index 07da443d6b..fa4b98bbf0 100644 --- a/pallets/public-credentials/src/lib.rs +++ b/pallets/public-credentials/src/lib.rs @@ -26,6 +26,7 @@ //! - [`Pallet`] #![cfg_attr(not(feature = "std"), no_std)] +pub mod assets; pub mod credentials; pub mod default_weights; @@ -66,12 +67,12 @@ pub mod pallet { // Maybe with #[transactional] we could stop caring and simply rollback if the two are the same and there is not enough // for both operations. pub(crate) type CurrencyOf = ::Currency; - // TODO: Replace with an enum that includes KILT DIDs and asset DIDs. pub(crate) type SubjectIdOf = ::SubjectId; pub type CredentialOf = Credential< CtypeHashOf, - SubjectIdOf, + // Input subject_id is a raw byte array, converted within the extrinsic + Vec, Vec, ClaimHashOf, H256, @@ -95,10 +96,8 @@ pub mod pallet { ::Origin, >; type Event: From> + IsType<::Event>; - #[pallet::constant] - type MaxEncodedClaimContentLength: Get; type OriginSuccess: CallSources, AttesterOf>; - type SubjectId: Parameter + MaxEncodedLen + TryFrom>; + type SubjectId: Parameter + MaxEncodedLen + TryFrom, Error = DispatchError>; type WeightInfo: WeightInfo; } @@ -146,6 +145,7 @@ pub mod pallet { UnableToPayFees, ClaimerInfoNotFound, InvalidClaimerSignature, + InvalidInput, InternalError, } @@ -173,6 +173,9 @@ pub mod pallet { authorization_info, } = *credential; + // Try to decode subject ID to something usable + let subject = T::SubjectId::try_from(subject)?; + // Check that the same attestation has not already been issued previously // (potentially to a different subject) ensure!( From c16dd8d54a002130bf5fde8b4a7f4bfe78155919 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Wed, 15 Jun 2022 16:41:01 +0200 Subject: [PATCH 012/138] feat: working on parsing the right chain_id information --- pallets/public-credentials/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pallets/public-credentials/src/lib.rs b/pallets/public-credentials/src/lib.rs index fa4b98bbf0..a7adbf3ef0 100644 --- a/pallets/public-credentials/src/lib.rs +++ b/pallets/public-credentials/src/lib.rs @@ -96,8 +96,9 @@ pub mod pallet { ::Origin, >; type Event: From> + IsType<::Event>; + type InputError: Into; type OriginSuccess: CallSources, AttesterOf>; - type SubjectId: Parameter + MaxEncodedLen + TryFrom, Error = DispatchError>; + type SubjectId: Parameter + MaxEncodedLen + TryFrom, Error = Self::InputError>; type WeightInfo: WeightInfo; } @@ -174,7 +175,7 @@ pub mod pallet { } = *credential; // Try to decode subject ID to something usable - let subject = T::SubjectId::try_from(subject)?; + let subject = T::SubjectId::try_from(subject).map_err(|e| e.into())?; // Check that the same attestation has not already been issued previously // (potentially to a different subject) From 6d07577fcc375232d7b6deee6a0f155d3a40f656 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Thu, 16 Jun 2022 09:41:55 +0200 Subject: [PATCH 013/138] feat: add dotsama and solana chains --- Cargo.lock | 2 ++ pallets/public-credentials/Cargo.toml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 593cf24be9..8f24e7457d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8263,10 +8263,12 @@ name = "public-credentials" version = "1.6.2" dependencies = [ "attestation", + "base58", "ctype", "frame-benchmarking", "frame-support", "frame-system", + "hex", "kilt-support", "log", "pallet-balances", diff --git a/pallets/public-credentials/Cargo.toml b/pallets/public-credentials/Cargo.toml index 61ae79c392..f27d5a34fb 100644 --- a/pallets/public-credentials/Cargo.toml +++ b/pallets/public-credentials/Cargo.toml @@ -19,6 +19,8 @@ sp-keystore = {branch = "polkadot-v0.9.23", git = "https://github.com/paritytech [dependencies] codec = {package = "parity-scale-codec", version = "3.1.2", default-features = false, features = ["derive"]} +base58 = {version = "0.2.0"} +hex = {default-features = false, version = "0.4.3"} log = "0.4.17" scale-info = {version = "2.1.1", default-features = false, features = ["derive"]} serde = {version = "1.0.137", optional = true} From 1ca4a9e35ceea8caad63b331695c8ba83f5f55b3 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Thu, 16 Jun 2022 11:00:45 +0200 Subject: [PATCH 014/138] feat: generic chain support --- pallets/public-credentials/src/assets.rs | 303 +++++++++++++++ pallets/public-credentials/src/mock.rs | 355 +++++++++++++++++ pallets/public-credentials/src/tests.rs | 467 +++++++++++++++++++++++ 3 files changed, 1125 insertions(+) create mode 100644 pallets/public-credentials/src/assets.rs create mode 100644 pallets/public-credentials/src/mock.rs create mode 100644 pallets/public-credentials/src/tests.rs diff --git a/pallets/public-credentials/src/assets.rs b/pallets/public-credentials/src/assets.rs new file mode 100644 index 0000000000..cfc5c957d1 --- /dev/null +++ b/pallets/public-credentials/src/assets.rs @@ -0,0 +1,303 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2022 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +pub mod chain_id { + + use base58::FromBase58; + + use frame_support::{BoundedVec, ensure, traits::ConstU32}; + use sp_runtime::traits::CheckedConversion; + use sp_std::str; + + use crate::{Error, Config}; + + const MINIMUM_NAMESPACE_LENGTH: u32 = 3; + const MAXIMUM_NAMESPACE_LENGTH: u32 = 8; + const MINIMUM_REFERENCE_LENGTH: u32 = 1; + const MAXIMUM_REFERENCE_LENGTH: u32 = 32; + + #[derive(std::fmt::Debug, PartialEq, Eq, PartialOrd, Ord)] + pub enum ChainId { + Eip155(Eip155Reference), + Bip122(GenesisHexHashReference), + Dotsama(GenesisHexHashReference), + Solana(GenesisBase58HashReference), + Generic(GenericChainId), + } + + impl TryFrom> for ChainId { + type Error = Error; + + fn try_from(value: Vec) -> Result { + match value.as_slice() { + // "eip155:" chains -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-3.md + [b'e', b'i', b'p', b'1', b'5', b'5', b':', chain_id @ ..] => Eip155Reference::::try_from(chain_id).and_then(|reference| Ok(Self::Eip155(reference))), + // "bip122:" chains -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-4.md + [b'b', b'i', b'p', b'1', b'2', b'2', b':', chain_id @ ..] => GenesisHexHashReference::::try_from(chain_id).and_then(|reference| Ok(Self::Bip122(reference))), + // "polkadot" chains -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-13.md + [b'p', b'o', b'l', b'k', b'a', b'd', b'o', b't', b':', chain_id @ ..] => GenesisHexHashReference::::try_from(chain_id).and_then(|reference| Ok(Self::Dotsama(reference))), + // "solana" chains -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-30.md + [b's', b'o', b'l', b'a', b'n', b'a', b':', chain_id @ ..] => GenesisBase58HashReference::::try_from(chain_id).and_then(|reference| Ok(Self::Solana(reference))), + chain_id => GenericChainId::::try_from(chain_id).and_then(|id| Ok(Self::Generic(id))), + } + } + } + + #[derive(sp_runtime::RuntimeDebug, PartialEq, Eq, PartialOrd, Ord)] + pub struct Eip155Reference(pub BoundedVec>, Option>); + + impl TryFrom<&[u8]> for Eip155Reference { + type Error = Error; + + fn try_from(value: &[u8]) -> Result { + let input_len = value.len().checked_into::().ok_or(Error::::InvalidInput)?; + ensure!( + input_len >= MINIMUM_REFERENCE_LENGTH && input_len <= MAXIMUM_REFERENCE_LENGTH, + Error::::InvalidInput + ); + value.iter().try_for_each(|c| { + if !(b'0'..=b'9').contains(&c) { + Err(Error::::InvalidInput) + } else { + Ok(()) + } + })?; + // Unwrapping since we just checked for length + Ok(Self(value.to_vec().try_into().unwrap(), None)) + } + } + + #[derive(sp_runtime::RuntimeDebug, PartialEq, Eq, PartialOrd, Ord)] + pub struct GenesisHexHashReference(pub [u8; L], Option>); + + impl TryFrom<&[u8]> for GenesisHexHashReference { + type Error = Error; + + fn try_from(value: &[u8]) -> Result { + let decoded = hex::decode(value).map_err(|_| Error::::InvalidInput)?; + let inner: [u8; L] = decoded.try_into().map_err(|_| Error::::InvalidInput)?; + Ok(Self(inner, None)) + } + } + + #[derive(sp_runtime::RuntimeDebug, PartialEq, Eq, PartialOrd, Ord)] + pub struct GenesisBase58HashReference(pub BoundedVec>, Option>); + + impl TryFrom<&[u8]> for GenesisBase58HashReference { + type Error = Error; + + fn try_from(value: &[u8]) -> Result { + let input_len = value.len().checked_into::().ok_or(Error::::InvalidInput)?; + ensure!( + input_len >= MINIMUM_REFERENCE_LENGTH && input_len <= MAXIMUM_REFERENCE_LENGTH, + Error::::InvalidInput + ); + let decoded_string = str::from_utf8(value).map_err(|_| Error::::InvalidInput)?; + // Check that the string is valid base58 + decoded_string.from_base58().map_err(|_| Error::::InvalidInput)?; + // Unwrapping since we just checked for length + Ok(Self(value.to_vec().try_into().unwrap(), None)) + } + } + + #[derive(sp_runtime::RuntimeDebug, PartialEq, Eq, PartialOrd, Ord)] + pub struct GenericChainId { + namespace: BoundedVec>, + reference: BoundedVec>, + _phantom: Option, + } + + impl GenericChainId { + fn from_components(namespace: BoundedVec>, reference: BoundedVec>) -> Self { + Self { + namespace, + reference, + _phantom: None + } + } + } + + impl TryFrom<&[u8]> for GenericChainId { + type Error = Error; + + fn try_from(value: &[u8]) -> Result { + ensure!( + value.len() <= (MAXIMUM_REFERENCE_LENGTH + MAXIMUM_NAMESPACE_LENGTH + 1) as usize, + Error::::InvalidInput + ); + let mut components = value.split(|c| *c == b':'); + + if let (Some(namespace), Some(reference)) = (components.next(), components.next()) { + let namespace_length = namespace.into_iter().try_fold(0u32, |length, c| { + let new_length = length + 1; + if new_length > MAXIMUM_NAMESPACE_LENGTH { + return Err(Error::::InvalidInput); + } + if !matches!(c, b'-' | b'a'..=b'z' | b'0'..=b'9') { + return Err(Error::::InvalidInput); + } + Ok(new_length) + })?; + if namespace_length < MINIMUM_NAMESPACE_LENGTH { + return Err(Error::::InvalidInput); + } + + let reference_length = reference.into_iter().try_fold(0u32, |length, c| { + let new_length = length + 1; + if new_length > MAXIMUM_REFERENCE_LENGTH { + return Err(Error::::InvalidInput); + } + if !matches!(c, b'-' | b'a'..=b'z' | b'A'..=b'Z' |b'0'..=b'9') { + return Err(Error::::InvalidInput); + } + Ok(new_length) + })?; + if reference_length < MINIMUM_REFERENCE_LENGTH { + return Err(Error::::InvalidInput); + } + Ok(Self::from_components(namespace.to_vec().try_into().unwrap(), reference.to_vec().try_into().unwrap())) + } else { + Err(Error::::InvalidInput) + } + } + } + + #[cfg(test)] + mod test { + use super::*; + + use crate::mock::Test; + + #[test] + fn test_eip155_chains() { + let valid_chains = ["eip155:1", "eip155:5", "eip155:99999999999999999999999999999999", "eip155:0"]; + for chain in valid_chains { + assert!(ChainId::::try_from(chain.as_bytes().to_vec()).is_ok(), "Chain ID {:?} should not fail to parse for eip155 chains", chain); + } + + let invalid_chains = [ + // Too short + "e", "ei", "eip", "eip1", "eip15", "eip155", "eip155:", + // Not a number + "eip155:a", "eip155::", "eip155:›", "eip155:😁", + // Max chars + 1 + "eip155:999999999999999999999999999999999" + ]; + for chain in invalid_chains { + assert!(ChainId::::try_from(chain.as_bytes().to_vec()).is_err(), "Chain ID {:?} should fail to parse for eip155 chains", chain); + } + } + + #[test] + fn test_bip122_chains() { + let valid_chains = ["bip122:000000000019d6689c085ae165831e93", "bip122:000000000019D6689C085AE165831E93", "bip122:12a765e31ffd4059bada1e25190f6e98", "bip122:fdbe99b90c90bae7505796461471d89a", "bip122:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]; + for chain in valid_chains { + assert!(ChainId::::try_from(chain.as_bytes().to_vec()).is_ok(), "Chain ID {:?} should not fail to parse for polkadot chains", chain); + } + + let invalid_chains = [ + // Too short + "b", "bi", "bip", "bip1", "bip12", "bip122", "bip122:", + // Not an HEX string + "bip122:gg", "bip122::", "bip122:›", "bip122:😁", + // Not the expected length + "bip122:a", "bip122:aa", "bip122:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ]; + for chain in invalid_chains { + assert!(ChainId::::try_from(chain.as_bytes().to_vec()).is_err(), "Chain ID {:?} should fail to parse for polkadot chains", chain); + } + } + + #[test] + fn test_dotsama_chains() { + let valid_chains = ["polkadot:b0a8d493285c2df73290dfb7e61f870f", "polkadot:B0A8D493285C2DF73290DFB7E61F870F", "polkadot:742a2ca70c2fda6cee4f8df98d64c4c6", "polkadot:37e1f8125397a98630013a4dff89b54c", "polkadot:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]; + for chain in valid_chains { + assert!(ChainId::::try_from(chain.as_bytes().to_vec()).is_ok(), "Chain ID {:?} should not fail to parse for polkadot chains", chain); + } + + let invalid_chains = [ + // Too short + "p", "po", "pol", "polk", "polka", "polkad", "polkado", "polkadot", "polkadot:", + // Not an HEX string + "polkadot:gg", "polkadot::", "polkadot:›", "polkadot:😁", + // Not the expected length + "polkadot:a", "polkadot:aa", "polkadot:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ]; + for chain in invalid_chains { + assert!(ChainId::::try_from(chain.as_bytes().to_vec()).is_err(), "Chain ID {:?} should fail to parse for polkadot chains", chain); + } + } + + #[test] + fn test_solana_chains() { + let valid_chains = ["solana:a", "solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ", "solana:8E9rvCKLFQia2Y35HXjjpWzj8weVo44K"]; + for chain in valid_chains { + assert!(ChainId::::try_from(chain.as_bytes().to_vec()).is_ok(), "Chain ID {:?} should not fail to parse for solana chains", chain); + } + + let invalid_chains = [ + // Too short + "s", "so", "sol", "sola", "solan", "solana", "solana:", + // Not a Base58 string + "solana::", "solana:›", "solana:😁", "solana:random-string", + // Valid base58 text, too long (34 chars) + "solana:TJ24pxm996UCBScuQRwjYo4wvPjUa8pzKo" + ]; + for chain in invalid_chains { + assert!(ChainId::::try_from(chain.as_bytes().to_vec()).is_err(), "Chain ID {:?} should fail to parse for generic chains", chain); + } + } + + #[test] + fn test_generic_chains() { + let valid_chains = [ + // Edge cases + "abc:-", "-as01-aa:A", "12345678:abcdefghjklmnopqrstuvwxyzABCD012", + // Filecoin examples -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-23.md + "fil:t", "fil:f", + // Tezos examples -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-26.md + "tezos:NetXdQprcVkpaWU", "tezos:NetXm8tYqnMWky1", + // Cosmos examples -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-5.md + "cosmos:cosmoshub-2", "cosmos:cosmoshub-3", "cosmos:Binance-Chain-Tigris", "cosmos:iov-mainnet", "cosmos:x", "cosmos:hash-", "cosmos:hashed", + // Lisk examples -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-6.md + "lip9:9ee11e9df416b18b", "lip9:e48feb88db5b5cf5", + // EOSIO examples -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-7.md + "eosio:aca376f206b8fc25a6ed44dbdc66547c", "eosio:e70aaab8997e1dfce58fbfac80cbbb8f", "eosio:4667b205c6838ef70ff7988f6e8257e8", "eosio:1eaa0824707c8c16bd25145493bf062a", + // Stellar examples -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-28.md + "stellar:testnet", "stellar:pubnet" + ]; + for chain in valid_chains { + println!("Testing right chain {:?}", chain); + assert!(ChainId::::try_from(chain.as_bytes().to_vec()).is_ok(), "Chain ID {:?} should not fail to parse for generic chains", chain); + } + + let invalid_chains = [ + // Too short + "a", "ab", "01:", "ab-:", + // Too long + "123456789:1", "12345678:123456789123456789123456789123456", "123456789:123456789123456789123456789123456", + // Unallowed characters + "::", "c?1:›", "de:😁", + ]; + for chain in invalid_chains { + println!("Testing wrong chain {:?}", chain); + assert!(ChainId::::try_from(chain.as_bytes().to_vec()).is_err(), "Chain ID {:?} should fail to parse for solana chains", chain); + } + } + } +} diff --git a/pallets/public-credentials/src/mock.rs b/pallets/public-credentials/src/mock.rs new file mode 100644 index 0000000000..6816909aa4 --- /dev/null +++ b/pallets/public-credentials/src/mock.rs @@ -0,0 +1,355 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2022 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use frame_support::traits::Get; + +use attestation::ClaimHashOf; +use ctype::CtypeHashOf; +use kilt_support::deposit::Deposit; + +use crate::{ + AccountIdOf, BalanceOf, Claim, CurrencyOf, ClaimerSignatureInfo, Config, CredentialEntryOf, CredentialOf, Credentials, + CredentialsUnicityIndex, SubjectIdOf, +}; + +pub(crate) type BlockNumber = u64; +pub(crate) type Hash = sp_core::H256; +pub(crate) type ClaimerSignatureInfoOf = + ClaimerSignatureInfo<::ClaimerIdentifier, ::ClaimerSignature>; + +pub fn generate_base_public_credential_creation_op( + subject_id: Vec, + claim_hash: ClaimHashOf, + ctype_hash: CtypeHashOf, + claimer_signature: Option>, +) -> CredentialOf { + CredentialOf:: { + claim: Claim { + ctype_hash, + subject: subject_id, + contents: vec![0; 32] + .try_into() + .expect("Vec should successfully be transformed into required BoundedVec."), + }, + claim_hash, + claimer_signature, + nonce: Default::default(), + authorization_info: Default::default(), + } +} + +pub fn insert_public_credentials( + subject_id: SubjectIdOf, + claim_hash: ClaimHashOf, + credential_entry: CredentialEntryOf, +) { + kilt_support::reserve_deposit::, CurrencyOf>(credential_entry.deposit.owner.clone(), credential_entry.deposit.amount) + .expect("Attester should have enough balance"); + + Credentials::::insert(&subject_id, &claim_hash, credential_entry); + CredentialsUnicityIndex::::insert(claim_hash, subject_id); +} + +pub fn generate_base_credential_entry( + payer: AccountIdOf, + block_number: ::BlockNumber, +) -> CredentialEntryOf { + CredentialEntryOf:: { + block_number, + deposit: Deposit::, BalanceOf> { + owner: payer, + amount: ::Deposit::get(), + }, + } +} + +#[cfg(test)] +pub use crate::mock::runtime::*; + +// Mocks that are only used internally +#[cfg(test)] +pub(crate) mod runtime { + use super::*; + + use codec::{Encode, Decode, MaxEncodedLen}; + use scale_info::TypeInfo; + use frame_support::{ + parameter_types, + traits::{ConstU128, ConstU16, ConstU32, ConstU64}, + weights::constants::RocksDbWeight, + }; + use sp_core::{sr25519, Pair,}; + use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, + MultiSignature, MultiSigner, + }; + + use kilt_support::{ + mock::{mock_origin, SubjectId}, + signature::EqualVerify, + }; + + use attestation::{mock::MockAccessControl, AttestationDetails, ClaimHashOf}; + use ctype::{CtypeCreatorOf, CtypeHashOf}; + + use crate::{AccountIdOf, BalanceOf, Error}; + + pub type Balance = u128; + pub type AccountPublic = ::Signer; + pub type AccountId = ::AccountId; + + #[derive(Default, Encode, Clone, Decode, MaxEncodedLen, sp_runtime::RuntimeDebug, Eq, PartialEq, Ord, PartialOrd, TypeInfo)] + pub struct TestSubjectId([u8; 32]); + + impl core::ops::Deref for TestSubjectId { + type Target = [u8; 32]; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl TryFrom> for TestSubjectId { + type Error = Error; + + fn try_from(value: Vec) -> Result { + let inner: [u8; 32] = value.try_into().map_err(|_| Error::::InvalidInput)?; + Ok(Self(inner)) + } + } + + impl From<[u8; 32]> for TestSubjectId { + fn from(value: [u8; 32]) -> Self { + Self(value) + } + } + + pub const MILLI_UNIT: Balance = 10u128.pow(12); + + frame_support::construct_runtime!( + pub enum Test where + Block = frame_system::mocking::MockBlock, + NodeBlock = frame_system::mocking::MockBlock, + UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Attestation: attestation::{Pallet, Call, Storage, Event}, + Ctype: ctype::{Pallet, Call, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Event}, + MockOrigin: mock_origin::{Pallet, Origin}, + PublicCredentials: crate::{Pallet, Call, Storage, Event}, + } + ); + + impl mock_origin::Config for Test { + type Origin = Origin; + type AccountId = AccountId; + type SubjectId = SubjectId; + } + + impl frame_system::Config for Test { + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = BlockNumber; + type Hash = Hash; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type Event = (); + type BlockHashCount = ConstU64<250>; + type DbWeight = RocksDbWeight; + type Version = (); + + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type BaseCallFilter = frame_support::traits::Everything; + type SystemWeightInfo = (); + type BlockWeights = (); + type BlockLength = (); + type SS58Prefix = ConstU16<38>; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; + } + + impl pallet_balances::Config for Test { + type Balance = Balance; + type DustRemoval = (); + type Event = (); + type ExistentialDeposit = ConstU128; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = ConstU32<5>; + type MaxReserves = ConstU32<5>; + type ReserveIdentifier = [u8; 8]; + } + + impl ctype::Config for Test { + type CtypeCreatorId = SubjectId; + type EnsureOrigin = mock_origin::EnsureDoubleOrigin; + type OriginSuccess = mock_origin::DoubleOrigin; + type Event = (); + type WeightInfo = (); + + type Currency = Balances; + type Fee = ConstU128<500>; + type FeeCollector = (); + } + + // FIXME: Re-replace with ConstU128 when compilation issue fixed. + parameter_types! { + pub const AttDeposit: Balance = 100 * MILLI_UNIT; + } + + impl attestation::Config for Test { + type EnsureOrigin = mock_origin::EnsureDoubleOrigin; + type OriginSuccess = mock_origin::DoubleOrigin; + type Event = (); + type WeightInfo = (); + + type Currency = Balances; + type Deposit = AttDeposit; + type MaxDelegatedAttestations = ConstU32<0>; + type AttesterId = SubjectId; + type AuthorizationId = SubjectId; + type AccessControl = MockAccessControl; + } + + parameter_types! { + pub const CredDeposit: Balance = 10 * MILLI_UNIT; + } + + impl Config for Test { + type ClaimerIdentifier = SubjectId; + type ClaimerSignature = (Self::ClaimerIdentifier, Vec); + type ClaimerSignatureVerification = EqualVerify>; + type Deposit = CredDeposit; + type EnsureOrigin = ::EnsureOrigin; + type Event = (); + type InputError = Error; + type OriginSuccess = ::OriginSuccess; + type SubjectId = TestSubjectId; + type WeightInfo = (); + } + + pub(crate) const ACCOUNT_00: AccountId = AccountId::new([1u8; 32]); + pub(crate) const ACCOUNT_01: AccountId = AccountId::new([2u8; 32]); + + pub(crate) const ALICE_SEED: [u8; 32] = [0u8; 32]; + pub(crate) const BOB_SEED: [u8; 32] = [1u8; 32]; + pub(crate) const CHARLIE_SEED: [u8; 32] = [2u8; 32]; + + pub(crate) const SUBJECT_ID_00: [u8; 32] = [100u8; 32]; + + pub(crate) const CLAIM_HASH_SEED_01: u64 = 1u64; + pub(crate) const CLAIM_HASH_SEED_02: u64 = 2u64; + + pub(crate) fn claim_hash_from_seed(seed: u64) -> Hash { + Hash::from_low_u64_be(seed) + } + + pub(crate) fn sr25519_did_from_seed(seed: &[u8; 32]) -> SubjectId { + MultiSigner::from(sr25519::Pair::from_seed(seed).public()) + .into_account() + .into() + } + + pub(crate) fn hash_to_u8(hash: Hash) -> Vec { + hash.encode() + } + + #[derive(Clone, Default)] + pub struct ExtBuilder { + /// initial ctypes & owners + ctypes: Vec<(CtypeHashOf, CtypeCreatorOf)>, + /// endowed accounts with balances + balances: Vec<(AccountIdOf, BalanceOf)>, + attestations: Vec<(ClaimHashOf, AttestationDetails)>, + public_credentials: Vec<(SubjectIdOf, ClaimHashOf, CredentialEntryOf)>, + } + + impl ExtBuilder { + #[must_use] + pub fn with_ctypes(mut self, ctypes: Vec<(CtypeHashOf, CtypeCreatorOf)>) -> Self { + self.ctypes = ctypes; + self + } + + #[must_use] + pub fn with_balances(mut self, balances: Vec<(AccountIdOf, BalanceOf)>) -> Self { + self.balances = balances; + self + } + + #[must_use] + pub fn with_attestations(mut self, attestations: Vec<(ClaimHashOf, AttestationDetails)>) -> Self { + self.attestations = attestations; + self + } + + #[must_use] + pub fn with_public_credentials( + mut self, + credentials: Vec<(SubjectIdOf, ClaimHashOf, CredentialEntryOf)>, + ) -> Self { + self.public_credentials = credentials; + self + } + + pub fn build(self) -> sp_io::TestExternalities { + let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + pallet_balances::GenesisConfig:: { + balances: self.balances.clone(), + } + .assimilate_storage(&mut storage) + .expect("assimilate should not fail"); + + let mut ext = sp_io::TestExternalities::new(storage); + + ext.execute_with(|| { + for ctype in self.ctypes { + ctype::Ctypes::::insert(ctype.0, ctype.1.clone()); + } + + for (claim_hash, details) in self.attestations { + attestation::mock::insert_attestation(claim_hash, details); + } + + for (subject_id, claim_hash, credential_entry) in self.public_credentials { + insert_public_credentials(subject_id, claim_hash, credential_entry); + } + }); + + ext + } + + #[cfg(feature = "runtime-benchmarks")] + pub fn build_with_keystore(self) -> sp_io::TestExternalities { + let mut ext = self.build(); + + let keystore = sp_keystore::testing::KeyStore::new(); + ext.register_extension(sp_keystore::KeystoreExt(std::sync::Arc::new(keystore))); + + ext + } + } +} diff --git a/pallets/public-credentials/src/tests.rs b/pallets/public-credentials/src/tests.rs new file mode 100644 index 0000000000..c58c1ad3b8 --- /dev/null +++ b/pallets/public-credentials/src/tests.rs @@ -0,0 +1,467 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2022 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use frame_support::{assert_noop, assert_ok}; +use sp_runtime::traits::Zero; + +use attestation::{attestations::AttestationDetails, mock::generate_base_attestation, Attestations}; +use ctype::mock::get_ctype_hash; +use kilt_support::mock::mock_origin::DoubleOrigin; + +use crate::{mock::*, Config, Credentials, CredentialsUnicityIndex, Error, SubjectIdOf}; + +// add + +#[test] +fn add_with_no_signature_successful() { + let attester = sr25519_did_from_seed(&ALICE_SEED); + let subject_id = SUBJECT_ID_00; + let claim_hash = claim_hash_from_seed(CLAIM_HASH_SEED_01); + let claim_hash_2 = claim_hash_from_seed(CLAIM_HASH_SEED_02); + let ctype_hash = get_ctype_hash::(true); + let new_credential_1 = + generate_base_public_credential_creation_op::(subject_id.into(), claim_hash, ctype_hash, None); + let new_credential_2 = + generate_base_public_credential_creation_op::(subject_id.into(), claim_hash_2, ctype_hash, None); + let public_credential_deposit = ::Deposit::get(); + let attestation_deposit = ::Deposit::get(); + + ExtBuilder::default() + .with_balances(vec![( + ACCOUNT_00, + (public_credential_deposit + attestation_deposit) * 2, + )]) + .with_ctypes(vec![(ctype_hash, attester.clone())]) + .build() + .execute_with(|| { + // Check for 0 reserved deposit + assert!(Balances::reserved_balance(ACCOUNT_00).is_zero()); + + assert_ok!(PublicCredentials::add( + DoubleOrigin(ACCOUNT_00, attester.clone()).into(), + Box::new(new_credential_1.clone()) + )); + let stored_attestation = + Attestations::::get(&claim_hash).expect("Attestation should be present on chain."); + let stored_public_credential_details = Credentials::::get(&TestSubjectId::try_from(subject_id.clone()).unwrap(), &claim_hash) + .expect("Public credential details should be present on chain."); + + // Test interactions with attestation pallet + assert_eq!(stored_attestation.ctype_hash, ctype_hash); + assert_eq!(stored_attestation.attester, attester); + + // Test this pallet logic + assert_eq!(stored_public_credential_details.block_number, 0); + assert_eq!( + CredentialsUnicityIndex::::get(&claim_hash), + Some(subject_id.into()) + ); + + // Check deposit reservation logic + assert_eq!(Balances::reserved_balance(ACCOUNT_00), public_credential_deposit + attestation_deposit); + + // Re-issuing the same credential will fail + assert_noop!( + PublicCredentials::add( + DoubleOrigin(ACCOUNT_00, attester.clone()).into(), + Box::new(new_credential_1.clone()) + ), + Error::::CredentialAlreadyIssued + ); + + // Check deposit has not changed + assert_eq!(Balances::reserved_balance(ACCOUNT_00), public_credential_deposit + attestation_deposit); + + System::set_block_number(1); + + // Issuing a completely new credential will work + assert_ok!(PublicCredentials::add( + DoubleOrigin(ACCOUNT_00, attester.clone()).into(), + Box::new(new_credential_2.clone()) + )); + + let stored_attestation = + Attestations::::get(&claim_hash_2).expect("Attestation #2 should be present on chain."); + let stored_public_credential_details = Credentials::::get(&TestSubjectId::try_from(subject_id.clone()).unwrap(), &claim_hash_2) + .expect("Public credential #2 details should be present on chain."); + + // Test interactions with attestation pallet + assert_eq!(stored_attestation.ctype_hash, ctype_hash); + assert_eq!(stored_attestation.attester, attester); + + // Test this pallet logic + assert_eq!(stored_public_credential_details.block_number, 1); + assert_eq!(CredentialsUnicityIndex::::get(&claim_hash_2), Some(subject_id.into())); + + // Deposit is 2x now + assert_eq!(Balances::reserved_balance(ACCOUNT_00), 2 * (public_credential_deposit + attestation_deposit)); + + // Deleting the attestation only from the attestation pallet will still fail + Attestation::reclaim_deposit(Origin::signed(ACCOUNT_00), claim_hash) + .expect("Attestation deposit reclaim should not fail"); + assert_noop!( + PublicCredentials::add( + DoubleOrigin(ACCOUNT_00, attester.clone()).into(), + Box::new(new_credential_1.clone()) + ), + Error::::CredentialAlreadyIssued + ); + + // Deposit should now be equal to 1 attestation + 2 public credentials + assert_eq!(Balances::reserved_balance(ACCOUNT_00), 2 * public_credential_deposit + attestation_deposit); + }); +} + +#[test] +fn add_with_claimer_signature_successful() { + let attester = sr25519_did_from_seed(&ALICE_SEED); + let subject_id = SUBJECT_ID_00; + let claim_hash = claim_hash_from_seed(CLAIM_HASH_SEED_01); + let ctype_hash = get_ctype_hash::(true); + let claimer_id = sr25519_did_from_seed(&BOB_SEED); + // FIXME: Change the definition of Signature so that we can simply use a tuple + // (claimer_id, claim_hash) as signature + let new_credential = generate_base_public_credential_creation_op::( + subject_id.into(), + claim_hash, + ctype_hash, + Some(ClaimerSignatureInfoOf:: { + claimer_id: claimer_id.clone(), + signature_payload: (claimer_id, hash_to_u8(claim_hash)), + }), + ); + let public_credential_deposit = ::Deposit::get(); + let attestation_deposit = ::Deposit::get(); + + ExtBuilder::default() + .with_balances(vec![(ACCOUNT_00, public_credential_deposit + attestation_deposit)]) + .with_ctypes(vec![(ctype_hash, attester.clone())]) + .build() + .execute_with(|| { + assert_ok!(PublicCredentials::add( + DoubleOrigin(ACCOUNT_00, attester.clone()).into(), + Box::new(new_credential.clone()) + )); + let stored_attestation = + Attestations::::get(&claim_hash).expect("Attestation should be present on chain."); + let stored_public_credential_details = Credentials::::get(&TestSubjectId::try_from(subject_id.clone()).unwrap(), &claim_hash) + .expect("Public credential details should be present on chain."); + + // Test interactions with attestation pallet + assert_eq!(stored_attestation.ctype_hash, ctype_hash); + assert_eq!(stored_attestation.attester, attester); + + // Test this pallet logic + assert_eq!(stored_public_credential_details.block_number, System::block_number()); + assert_eq!(CredentialsUnicityIndex::::get(&claim_hash), Some(subject_id.try_into().unwrap())); + }); +} + +#[test] +fn add_not_enough_balance() { + let attester = sr25519_did_from_seed(&ALICE_SEED); + let subject_id = SUBJECT_ID_00; + let claim_hash = claim_hash_from_seed(CLAIM_HASH_SEED_01); + let ctype_hash = get_ctype_hash::(true); + let new_credential = generate_base_public_credential_creation_op::(subject_id.into(), claim_hash, ctype_hash, None); + let public_credential_deposit = ::Deposit::get(); + let attestation_deposit = ::Deposit::get(); + + ExtBuilder::default() + // One less than the minimum required + .with_balances(vec![(ACCOUNT_00, public_credential_deposit + attestation_deposit - 1)]) + .with_ctypes(vec![(ctype_hash, attester.clone())]) + .build() + .execute_with(|| { + assert_noop!( + PublicCredentials::add( + DoubleOrigin(ACCOUNT_00, attester.clone()).into(), + Box::new(new_credential.clone()) + ), + Error::::UnableToPayFees + ); + }); +} + +#[test] +fn add_invalid_signature() { + let attester = sr25519_did_from_seed(&ALICE_SEED); + let subject_id = SUBJECT_ID_00; + let claim_hash = claim_hash_from_seed(CLAIM_HASH_SEED_01); + let ctype_hash = get_ctype_hash::(true); + let claimer_id = sr25519_did_from_seed(&CHARLIE_SEED); + let new_credential = generate_base_public_credential_creation_op::( + subject_id.into(), + claim_hash, + ctype_hash, + Some(ClaimerSignatureInfoOf:: { + claimer_id, + signature_payload: (sr25519_did_from_seed(&BOB_SEED), hash_to_u8(claim_hash)), + }), + ); + let public_credential_deposit = ::Deposit::get(); + let attestation_deposit = ::Deposit::get(); + + ExtBuilder::default() + .with_balances(vec![(ACCOUNT_00, public_credential_deposit + attestation_deposit)]) + .with_ctypes(vec![(ctype_hash, attester.clone())]) + .build() + .execute_with(|| { + assert_noop!( + PublicCredentials::add( + DoubleOrigin(ACCOUNT_00, attester.clone()).into(), + Box::new(new_credential.clone()) + ), + Error::::InvalidClaimerSignature + ); + }); +} + +#[test] +fn add_ctype_not_found() { + let attester = sr25519_did_from_seed(&ALICE_SEED); + let subject_id = SUBJECT_ID_00; + let claim_hash = claim_hash_from_seed(CLAIM_HASH_SEED_01); + let ctype_hash = get_ctype_hash::(true); + let claimer_id = sr25519_did_from_seed(&CHARLIE_SEED); + let new_credential = generate_base_public_credential_creation_op::( + subject_id.into(), + claim_hash, + ctype_hash, + Some(ClaimerSignatureInfoOf:: { + claimer_id: claimer_id.clone(), + signature_payload: (claimer_id, hash_to_u8(claim_hash)), + }), + ); + let public_credential_deposit = ::Deposit::get(); + let attestation_deposit = ::Deposit::get(); + + ExtBuilder::default() + .with_balances(vec![(ACCOUNT_00, public_credential_deposit + attestation_deposit)]) + .build() + .execute_with(|| { + assert_noop!( + PublicCredentials::add( + DoubleOrigin(ACCOUNT_00, attester.clone()).into(), + Box::new(new_credential.clone()) + ), + ctype::Error::::CTypeNotFound + ); + }); +} + +#[test] +fn add_invalid_subject_id() { + let attester = sr25519_did_from_seed(&ALICE_SEED); + let claim_hash = claim_hash_from_seed(CLAIM_HASH_SEED_01); + let ctype_hash = get_ctype_hash::(true); + let claimer_id = sr25519_did_from_seed(&CHARLIE_SEED); + let new_credential = generate_base_public_credential_creation_op::( + // 33 != expected size 32 -> triggers InvalidInput error + vec![200u8; 33].into(), + claim_hash, + ctype_hash, + Some(ClaimerSignatureInfoOf:: { + claimer_id: claimer_id.clone(), + signature_payload: (claimer_id, hash_to_u8(claim_hash)), + }), + ); + let public_credential_deposit = ::Deposit::get(); + let attestation_deposit = ::Deposit::get(); + + ExtBuilder::default() + .with_balances(vec![(ACCOUNT_00, public_credential_deposit + attestation_deposit)]) + .build() + .execute_with(|| { + assert_noop!( + PublicCredentials::add( + DoubleOrigin(ACCOUNT_00, attester.clone()).into(), + Box::new(new_credential.clone()) + ), + Error::::InvalidInput + ); + }); +} + +// remove + +#[test] +fn remove_successful() { + let attester = sr25519_did_from_seed(&ALICE_SEED); + let subject_id: SubjectIdOf = SUBJECT_ID_00.try_into().expect("Should not fail to parse test SubjectId."); + let claim_hash = claim_hash_from_seed(CLAIM_HASH_SEED_01); + let attestation: AttestationDetails = generate_base_attestation(attester.clone(), ACCOUNT_00); + let new_credential = generate_base_credential_entry(ACCOUNT_00, 0); + let public_credential_deposit = ::Deposit::get(); + let attestation_deposit = ::Deposit::get(); + + ExtBuilder::default() + .with_balances(vec![(ACCOUNT_00, public_credential_deposit + attestation_deposit)]) + .with_ctypes(vec![(attestation.ctype_hash, attester.clone())]) + .with_attestations(vec![(claim_hash, attestation.clone())]) + .with_public_credentials(vec![(subject_id.clone(), claim_hash, new_credential)]) + .build() + .execute_with(|| { + assert_ok!(PublicCredentials::remove( + DoubleOrigin(ACCOUNT_00, attester.clone()).into(), + claim_hash, + None + )); + // Test interactions with attestation pallet + assert_eq!(Attestations::::get(&claim_hash), None); + + // Test this pallet logic + assert_eq!(Credentials::::get(&TestSubjectId::try_from(subject_id.clone()).unwrap(), &claim_hash), None); + assert_eq!(CredentialsUnicityIndex::::get(&claim_hash), None); + + // Check deposit release logic + assert!(Balances::reserved_balance(ACCOUNT_00).is_zero()); + + // Removing the same credential again will fail + assert_noop!( + PublicCredentials::remove(DoubleOrigin(ACCOUNT_00, attester.clone()).into(), claim_hash, None), + Error::::CredentialNotFound + ); + + // Adding only the attestation without the credential will also fail to remove + // the credential. + Attestation::add( + DoubleOrigin(ACCOUNT_00, attester.clone()).into(), + claim_hash, + attestation.ctype_hash, + None, + ) + .expect("Adding the same attestation again should not fail"); + + assert_noop!( + PublicCredentials::remove(DoubleOrigin(ACCOUNT_00, attester.clone()).into(), claim_hash, None), + Error::::CredentialNotFound + ); + + // Check that only the attestation deposit is reserved + assert_eq!(Balances::reserved_balance(ACCOUNT_00), attestation_deposit); + }); +} + +#[test] +fn remove_unauthorized() { + let attester = sr25519_did_from_seed(&ALICE_SEED); + let wrong_attester = sr25519_did_from_seed(&BOB_SEED); + let subject_id: SubjectIdOf = SUBJECT_ID_00.try_into().expect("Should not fail to parse test SubjectId."); + let claim_hash = claim_hash_from_seed(CLAIM_HASH_SEED_01); + let attestation: AttestationDetails = generate_base_attestation(attester.clone(), ACCOUNT_00); + let new_credential = generate_base_credential_entry(ACCOUNT_00, 0); + let public_credential_deposit = ::Deposit::get(); + let attestation_deposit = ::Deposit::get(); + + ExtBuilder::default() + .with_balances(vec![(ACCOUNT_00, public_credential_deposit + attestation_deposit)]) + .with_ctypes(vec![(attestation.ctype_hash, attester)]) + .with_attestations(vec![(claim_hash, attestation)]) + .with_public_credentials(vec![(subject_id, claim_hash, new_credential)]) + .build() + .execute_with(|| { + assert_noop!( + PublicCredentials::remove(DoubleOrigin(ACCOUNT_00, wrong_attester).into(), claim_hash, None), + attestation::Error::::Unauthorized + ); + }); +} + +// reclaim_deposit + +#[test] +fn reclaim_deposit_successful() { + let attester = sr25519_did_from_seed(&ALICE_SEED); + let subject_id: SubjectIdOf = SUBJECT_ID_00.try_into().expect("Should not fail to parse test SubjectId."); + let claim_hash = claim_hash_from_seed(CLAIM_HASH_SEED_01); + let attestation: AttestationDetails = generate_base_attestation(attester.clone(), ACCOUNT_00); + let new_credential = generate_base_credential_entry(ACCOUNT_00, 0); + let public_credential_deposit = ::Deposit::get(); + let attestation_deposit = ::Deposit::get(); + + ExtBuilder::default() + .with_balances(vec![(ACCOUNT_00, public_credential_deposit + attestation_deposit)]) + .with_ctypes(vec![(attestation.ctype_hash, attester.clone())]) + .with_attestations(vec![(claim_hash, attestation.clone())]) + .with_public_credentials(vec![(subject_id.clone(), claim_hash, new_credential)]) + .build() + .execute_with(|| { + assert_ok!(PublicCredentials::reclaim_deposit( + Origin::signed(ACCOUNT_00), + claim_hash + )); + // Test interactions with attestation pallet + assert_eq!(Attestations::::get(&claim_hash), None); + + // Test this pallet logic + assert_eq!(Credentials::::get(&TestSubjectId::try_from(subject_id.clone()).unwrap(), &claim_hash), None); + assert_eq!(CredentialsUnicityIndex::::get(&claim_hash), None); + + // Check deposit release logic + assert!(Balances::reserved_balance(ACCOUNT_00).is_zero()); + + // Reclaiming the deposit for the same credential again will fail + assert_noop!( + PublicCredentials::reclaim_deposit(Origin::signed(ACCOUNT_00), claim_hash), + Error::::CredentialNotFound + ); + + // Adding only the attestation without the credential will also fail to reclaim + // the deposit for the credential. + Attestation::add( + DoubleOrigin(ACCOUNT_00, attester.clone()).into(), + claim_hash, + attestation.ctype_hash, + None, + ) + .expect("Adding the same attestation again should not fail"); + + assert_noop!( + PublicCredentials::reclaim_deposit(Origin::signed(ACCOUNT_00), claim_hash), + Error::::CredentialNotFound + ); + + // Check that only the attestation deposit is reserved + assert_eq!(Balances::reserved_balance(ACCOUNT_00), attestation_deposit); + }); +} + +#[test] +fn reclaim_deposit_unauthorized() { + let attester = sr25519_did_from_seed(&ALICE_SEED); + let subject_id: SubjectIdOf = SUBJECT_ID_00.try_into().expect("Should not fail to parse test SubjectId."); + let claim_hash = claim_hash_from_seed(CLAIM_HASH_SEED_01); + let attestation: AttestationDetails = generate_base_attestation(attester.clone(), ACCOUNT_00); + let new_credential = generate_base_credential_entry(ACCOUNT_00, 0); + let public_credential_deposit = ::Deposit::get(); + let attestation_deposit = ::Deposit::get(); + + ExtBuilder::default() + .with_balances(vec![(ACCOUNT_00, public_credential_deposit + attestation_deposit)]) + .with_ctypes(vec![(attestation.ctype_hash, attester)]) + .with_attestations(vec![(claim_hash, attestation)]) + .with_public_credentials(vec![(subject_id, claim_hash, new_credential)]) + .build() + .execute_with(|| { + assert_noop!( + PublicCredentials::reclaim_deposit(Origin::signed(ACCOUNT_01), claim_hash), + attestation::Error::::Unauthorized + ); + }); +} From bc3b0c6a66069af45432497c4b80b4721346901c Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Thu, 16 Jun 2022 11:28:27 +0200 Subject: [PATCH 015/138] feat: add utility functions for chain IDs --- pallets/public-credentials/src/assets.rs | 92 ++++++++++++++++++++++-- 1 file changed, 85 insertions(+), 7 deletions(-) diff --git a/pallets/public-credentials/src/assets.rs b/pallets/public-credentials/src/assets.rs index cfc5c957d1..1e653366ef 100644 --- a/pallets/public-credentials/src/assets.rs +++ b/pallets/public-credentials/src/assets.rs @@ -34,8 +34,8 @@ pub mod chain_id { #[derive(std::fmt::Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum ChainId { Eip155(Eip155Reference), - Bip122(GenesisHexHashReference), - Dotsama(GenesisHexHashReference), + Bip122(GenesisHexHashReference), + Dotsama(GenesisHexHashReference), Solana(GenesisBase58HashReference), Generic(GenericChainId), } @@ -48,9 +48,9 @@ pub mod chain_id { // "eip155:" chains -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-3.md [b'e', b'i', b'p', b'1', b'5', b'5', b':', chain_id @ ..] => Eip155Reference::::try_from(chain_id).and_then(|reference| Ok(Self::Eip155(reference))), // "bip122:" chains -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-4.md - [b'b', b'i', b'p', b'1', b'2', b'2', b':', chain_id @ ..] => GenesisHexHashReference::::try_from(chain_id).and_then(|reference| Ok(Self::Bip122(reference))), + [b'b', b'i', b'p', b'1', b'2', b'2', b':', chain_id @ ..] => GenesisHexHashReference::::try_from(chain_id).and_then(|reference| Ok(Self::Bip122(reference))), // "polkadot" chains -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-13.md - [b'p', b'o', b'l', b'k', b'a', b'd', b'o', b't', b':', chain_id @ ..] => GenesisHexHashReference::::try_from(chain_id).and_then(|reference| Ok(Self::Dotsama(reference))), + [b'p', b'o', b'l', b'k', b'a', b'd', b'o', b't', b':', chain_id @ ..] => GenesisHexHashReference::::try_from(chain_id).and_then(|reference| Ok(Self::Dotsama(reference))), // "solana" chains -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-30.md [b's', b'o', b'l', b'a', b'n', b'a', b':', chain_id @ ..] => GenesisBase58HashReference::::try_from(chain_id).and_then(|reference| Ok(Self::Solana(reference))), chain_id => GenericChainId::::try_from(chain_id).and_then(|id| Ok(Self::Generic(id))), @@ -58,9 +58,52 @@ pub mod chain_id { } } + impl ChainId { + pub fn ethereum_mainnet() -> Self { + Self::Eip155(Eip155Reference::::from_slice_unsafe(b"1")) + } + + pub fn moonriver_eth() -> Self { + // Info taken from https://chainlist.org/ + Self::Eip155(Eip155Reference::::from_slice_unsafe(b"1285")) + } + + pub fn moonbeam_eth() -> Self { + // Info taken from https://chainlist.org/ + Self::Eip155(Eip155Reference::::from_slice_unsafe(b"1284")) + } + + pub fn bitcoin_mainnet() -> Self { + Self::Bip122(GenesisHexHashReference::::from_slice_unsafe(b"000000000019d6689c085ae165831e93")) + } + + pub fn polkadot() -> Self { + Self::Dotsama(GenesisHexHashReference::::from_slice_unsafe(b"91b171bb158e2d3848fa23a9f1c25182")) + } + + pub fn kusama() -> Self { + Self::Dotsama(GenesisHexHashReference::::from_slice_unsafe(b"b0a8d493285c2df73290dfb7e61f870f")) + } + + pub fn kilt_spiritnet() -> Self { + Self::Dotsama(GenesisHexHashReference::::from_slice_unsafe(b"411f057b9107718c9624d6aa4a3f23c1")) + } + + pub fn solana_mainnet() -> Self { + Self::Solana(GenesisBase58HashReference::::from_slice_unsafe(b"4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")) + } + } + #[derive(sp_runtime::RuntimeDebug, PartialEq, Eq, PartialOrd, Ord)] pub struct Eip155Reference(pub BoundedVec>, Option>); + impl Eip155Reference { + #[allow(dead_code)] + pub(crate) fn from_slice_unsafe(slice: &[u8]) -> Self { + Self(slice.to_vec().try_into().unwrap(), None) + } + } + impl TryFrom<&[u8]> for Eip155Reference { type Error = Error; @@ -83,14 +126,22 @@ pub mod chain_id { } #[derive(sp_runtime::RuntimeDebug, PartialEq, Eq, PartialOrd, Ord)] - pub struct GenesisHexHashReference(pub [u8; L], Option>); + pub struct GenesisHexHashReference(pub [u8; L], Option>); + + impl GenesisHexHashReference { + #[allow(dead_code)] + pub(crate) fn from_slice_unsafe(slice: &[u8]) -> Self { + Self(slice.try_into().unwrap(), None) + } + } impl TryFrom<&[u8]> for GenesisHexHashReference { type Error = Error; fn try_from(value: &[u8]) -> Result { - let decoded = hex::decode(value).map_err(|_| Error::::InvalidInput)?; - let inner: [u8; L] = decoded.try_into().map_err(|_| Error::::InvalidInput)?; + // Verify it's a valid HEX string + hex::decode(value).map_err(|_| Error::::InvalidInput)?; + let inner: [u8; L] = value.try_into().map_err(|_| Error::::InvalidInput)?; Ok(Self(inner, None)) } } @@ -98,6 +149,13 @@ pub mod chain_id { #[derive(sp_runtime::RuntimeDebug, PartialEq, Eq, PartialOrd, Ord)] pub struct GenesisBase58HashReference(pub BoundedVec>, Option>); + impl GenesisBase58HashReference { + #[allow(dead_code)] + pub(crate) fn from_slice_unsafe(slice: &[u8]) -> Self { + Self(slice.to_vec().try_into().unwrap(), None) + } + } + impl TryFrom<&[u8]> for GenesisBase58HashReference { type Error = Error; @@ -123,6 +181,14 @@ pub mod chain_id { } impl GenericChainId { + #[allow(dead_code)] + fn from_components_unsafe(namespace:&[u8], reference: &[u8]) -> Self { + Self { + namespace: namespace.to_vec().try_into().unwrap(), + reference: reference.to_vec().try_into().unwrap(), + _phantom: None + } + } fn from_components(namespace: BoundedVec>, reference: BoundedVec>) -> Self { Self { namespace, @@ -299,5 +365,17 @@ pub mod chain_id { assert!(ChainId::::try_from(chain.as_bytes().to_vec()).is_err(), "Chain ID {:?} should fail to parse for solana chains", chain); } } + + #[test] + fn test_utility_functions() { + // These functions should never crash. We just check that here. + // ChainId::::ethereum_mainnet(); + // ChainId::::moonbeam_eth(); + ChainId::::bitcoin_mainnet(); + // ChainId::::polkadot(); + // ChainId::::kusama(); + // ChainId::::kilt_spiritnet(); + // ChainId::::solana_mainnet(); + } } } From ba3fb494f61b9c5dcaef7d6364306329bb9267c6 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Thu, 16 Jun 2022 12:04:49 +0200 Subject: [PATCH 016/138] chore: clippy + fmt --- pallets/attestation/src/mock.rs | 7 +- pallets/public-credentials/src/assets.rs | 279 ++++++++++++++---- pallets/public-credentials/src/credentials.rs | 10 +- pallets/public-credentials/src/lib.rs | 35 ++- pallets/public-credentials/src/mock.rs | 31 +- pallets/public-credentials/src/tests.rs | 74 +++-- support/src/deposit.rs | 7 +- 7 files changed, 335 insertions(+), 108 deletions(-) diff --git a/pallets/attestation/src/mock.rs b/pallets/attestation/src/mock.rs index 797fa05632..b1bc37dc3e 100644 --- a/pallets/attestation/src/mock.rs +++ b/pallets/attestation/src/mock.rs @@ -141,8 +141,11 @@ where } pub fn insert_attestation(claim_hash: ClaimHashOf, details: AttestationDetails) { - kilt_support::reserve_deposit::, CurrencyOf>(details.deposit.owner.clone(), details.deposit.amount) - .expect("Should have balance"); + kilt_support::reserve_deposit::, CurrencyOf>( + details.deposit.owner.clone(), + details.deposit.amount, + ) + .expect("Should have balance"); crate::Attestations::::insert(&claim_hash, details.clone()); if let Some(delegation_id) = details.authorization_id.as_ref() { diff --git a/pallets/public-credentials/src/assets.rs b/pallets/public-credentials/src/assets.rs index 1e653366ef..0543ccd8a6 100644 --- a/pallets/public-credentials/src/assets.rs +++ b/pallets/public-credentials/src/assets.rs @@ -20,11 +20,11 @@ pub mod chain_id { use base58::FromBase58; - use frame_support::{BoundedVec, ensure, traits::ConstU32}; + use frame_support::{ensure, traits::ConstU32, BoundedVec}; use sp_runtime::traits::CheckedConversion; use sp_std::str; - use crate::{Error, Config}; + use crate::{Config, Error}; const MINIMUM_NAMESPACE_LENGTH: u32 = 3; const MAXIMUM_NAMESPACE_LENGTH: u32 = 8; @@ -41,19 +41,27 @@ pub mod chain_id { } impl TryFrom> for ChainId { - type Error = Error; + type Error = Error; fn try_from(value: Vec) -> Result { match value.as_slice() { // "eip155:" chains -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-3.md - [b'e', b'i', b'p', b'1', b'5', b'5', b':', chain_id @ ..] => Eip155Reference::::try_from(chain_id).and_then(|reference| Ok(Self::Eip155(reference))), + [b'e', b'i', b'p', b'1', b'5', b'5', b':', chain_id @ ..] => { + Eip155Reference::::try_from(chain_id).map(|reference| Self::Eip155(reference)) + } // "bip122:" chains -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-4.md - [b'b', b'i', b'p', b'1', b'2', b'2', b':', chain_id @ ..] => GenesisHexHashReference::::try_from(chain_id).and_then(|reference| Ok(Self::Bip122(reference))), + [b'b', b'i', b'p', b'1', b'2', b'2', b':', chain_id @ ..] => { + GenesisHexHashReference::::try_from(chain_id).map(|reference| Self::Bip122(reference)) + } // "polkadot" chains -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-13.md - [b'p', b'o', b'l', b'k', b'a', b'd', b'o', b't', b':', chain_id @ ..] => GenesisHexHashReference::::try_from(chain_id).and_then(|reference| Ok(Self::Dotsama(reference))), + [b'p', b'o', b'l', b'k', b'a', b'd', b'o', b't', b':', chain_id @ ..] => { + GenesisHexHashReference::::try_from(chain_id).map(|reference| Self::Dotsama(reference)) + } // "solana" chains -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-30.md - [b's', b'o', b'l', b'a', b'n', b'a', b':', chain_id @ ..] => GenesisBase58HashReference::::try_from(chain_id).and_then(|reference| Ok(Self::Solana(reference))), - chain_id => GenericChainId::::try_from(chain_id).and_then(|id| Ok(Self::Generic(id))), + [b's', b'o', b'l', b'a', b'n', b'a', b':', chain_id @ ..] => { + GenesisBase58HashReference::::try_from(chain_id).map(|reference| Self::Solana(reference)) + } + chain_id => GenericChainId::::try_from(chain_id).map(|id| Self::Generic(id)), } } } @@ -74,28 +82,41 @@ pub mod chain_id { } pub fn bitcoin_mainnet() -> Self { - Self::Bip122(GenesisHexHashReference::::from_slice_unsafe(b"000000000019d6689c085ae165831e93")) + Self::Bip122(GenesisHexHashReference::::from_slice_unsafe( + b"000000000019d6689c085ae165831e93", + )) } pub fn polkadot() -> Self { - Self::Dotsama(GenesisHexHashReference::::from_slice_unsafe(b"91b171bb158e2d3848fa23a9f1c25182")) + Self::Dotsama(GenesisHexHashReference::::from_slice_unsafe( + b"91b171bb158e2d3848fa23a9f1c25182", + )) } pub fn kusama() -> Self { - Self::Dotsama(GenesisHexHashReference::::from_slice_unsafe(b"b0a8d493285c2df73290dfb7e61f870f")) + Self::Dotsama(GenesisHexHashReference::::from_slice_unsafe( + b"b0a8d493285c2df73290dfb7e61f870f", + )) } pub fn kilt_spiritnet() -> Self { - Self::Dotsama(GenesisHexHashReference::::from_slice_unsafe(b"411f057b9107718c9624d6aa4a3f23c1")) + Self::Dotsama(GenesisHexHashReference::::from_slice_unsafe( + b"411f057b9107718c9624d6aa4a3f23c1", + )) } pub fn solana_mainnet() -> Self { - Self::Solana(GenesisBase58HashReference::::from_slice_unsafe(b"4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")) + Self::Solana(GenesisBase58HashReference::::from_slice_unsafe( + b"4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ", + )) } } #[derive(sp_runtime::RuntimeDebug, PartialEq, Eq, PartialOrd, Ord)] - pub struct Eip155Reference(pub BoundedVec>, Option>); + pub struct Eip155Reference( + pub BoundedVec>, + Option>, + ); impl Eip155Reference { #[allow(dead_code)] @@ -110,11 +131,11 @@ pub mod chain_id { fn try_from(value: &[u8]) -> Result { let input_len = value.len().checked_into::().ok_or(Error::::InvalidInput)?; ensure!( - input_len >= MINIMUM_REFERENCE_LENGTH && input_len <= MAXIMUM_REFERENCE_LENGTH, + (MINIMUM_REFERENCE_LENGTH..=MAXIMUM_REFERENCE_LENGTH).contains(&input_len), Error::::InvalidInput ); value.iter().try_for_each(|c| { - if !(b'0'..=b'9').contains(&c) { + if !(b'0'..=b'9').contains(c) { Err(Error::::InvalidInput) } else { Ok(()) @@ -147,7 +168,10 @@ pub mod chain_id { } #[derive(sp_runtime::RuntimeDebug, PartialEq, Eq, PartialOrd, Ord)] - pub struct GenesisBase58HashReference(pub BoundedVec>, Option>); + pub struct GenesisBase58HashReference( + pub BoundedVec>, + Option>, + ); impl GenesisBase58HashReference { #[allow(dead_code)] @@ -162,7 +186,7 @@ pub mod chain_id { fn try_from(value: &[u8]) -> Result { let input_len = value.len().checked_into::().ok_or(Error::::InvalidInput)?; ensure!( - input_len >= MINIMUM_REFERENCE_LENGTH && input_len <= MAXIMUM_REFERENCE_LENGTH, + (MINIMUM_REFERENCE_LENGTH..=MAXIMUM_REFERENCE_LENGTH).contains(&input_len), Error::::InvalidInput ); let decoded_string = str::from_utf8(value).map_err(|_| Error::::InvalidInput)?; @@ -182,18 +206,21 @@ pub mod chain_id { impl GenericChainId { #[allow(dead_code)] - fn from_components_unsafe(namespace:&[u8], reference: &[u8]) -> Self { + fn from_components_unsafe(namespace: &[u8], reference: &[u8]) -> Self { Self { namespace: namespace.to_vec().try_into().unwrap(), reference: reference.to_vec().try_into().unwrap(), - _phantom: None + _phantom: None, } } - fn from_components(namespace: BoundedVec>, reference: BoundedVec>) -> Self { + fn from_components( + namespace: BoundedVec>, + reference: BoundedVec>, + ) -> Self { Self { namespace, reference, - _phantom: None + _phantom: None, } } } @@ -208,8 +235,8 @@ pub mod chain_id { ); let mut components = value.split(|c| *c == b':'); - if let (Some(namespace), Some(reference)) = (components.next(), components.next()) { - let namespace_length = namespace.into_iter().try_fold(0u32, |length, c| { + if let (Some(namespace), Some(reference)) = (components.next(), components.next()) { + let namespace_length = namespace.iter().try_fold(0u32, |length, c| { let new_length = length + 1; if new_length > MAXIMUM_NAMESPACE_LENGTH { return Err(Error::::InvalidInput); @@ -223,7 +250,7 @@ pub mod chain_id { return Err(Error::::InvalidInput); } - let reference_length = reference.into_iter().try_fold(0u32, |length, c| { + let reference_length = reference.iter().try_fold(0u32, |length, c| { let new_length = length + 1; if new_length > MAXIMUM_REFERENCE_LENGTH { return Err(Error::::InvalidInput); @@ -236,7 +263,10 @@ pub mod chain_id { if reference_length < MINIMUM_REFERENCE_LENGTH { return Err(Error::::InvalidInput); } - Ok(Self::from_components(namespace.to_vec().try_into().unwrap(), reference.to_vec().try_into().unwrap())) + Ok(Self::from_components( + namespace.to_vec().try_into().unwrap(), + reference.to_vec().try_into().unwrap(), + )) } else { Err(Error::::InvalidInput) } @@ -251,81 +281,176 @@ pub mod chain_id { #[test] fn test_eip155_chains() { - let valid_chains = ["eip155:1", "eip155:5", "eip155:99999999999999999999999999999999", "eip155:0"]; + let valid_chains = [ + "eip155:1", + "eip155:5", + "eip155:99999999999999999999999999999999", + "eip155:0", + ]; for chain in valid_chains { - assert!(ChainId::::try_from(chain.as_bytes().to_vec()).is_ok(), "Chain ID {:?} should not fail to parse for eip155 chains", chain); + assert!( + ChainId::::try_from(chain.as_bytes().to_vec()).is_ok(), + "Chain ID {:?} should not fail to parse for eip155 chains", + chain + ); } let invalid_chains = [ // Too short - "e", "ei", "eip", "eip1", "eip15", "eip155", "eip155:", + "e", + "ei", + "eip", + "eip1", + "eip15", + "eip155", + "eip155:", // Not a number - "eip155:a", "eip155::", "eip155:›", "eip155:😁", + "eip155:a", + "eip155::", + "eip155:›", + "eip155:😁", // Max chars + 1 - "eip155:999999999999999999999999999999999" + "eip155:999999999999999999999999999999999", ]; for chain in invalid_chains { - assert!(ChainId::::try_from(chain.as_bytes().to_vec()).is_err(), "Chain ID {:?} should fail to parse for eip155 chains", chain); + assert!( + ChainId::::try_from(chain.as_bytes().to_vec()).is_err(), + "Chain ID {:?} should fail to parse for eip155 chains", + chain + ); } } #[test] fn test_bip122_chains() { - let valid_chains = ["bip122:000000000019d6689c085ae165831e93", "bip122:000000000019D6689C085AE165831E93", "bip122:12a765e31ffd4059bada1e25190f6e98", "bip122:fdbe99b90c90bae7505796461471d89a", "bip122:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]; + let valid_chains = [ + "bip122:000000000019d6689c085ae165831e93", + "bip122:000000000019D6689C085AE165831E93", + "bip122:12a765e31ffd4059bada1e25190f6e98", + "bip122:fdbe99b90c90bae7505796461471d89a", + "bip122:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + ]; for chain in valid_chains { - assert!(ChainId::::try_from(chain.as_bytes().to_vec()).is_ok(), "Chain ID {:?} should not fail to parse for polkadot chains", chain); + assert!( + ChainId::::try_from(chain.as_bytes().to_vec()).is_ok(), + "Chain ID {:?} should not fail to parse for polkadot chains", + chain + ); } let invalid_chains = [ // Too short - "b", "bi", "bip", "bip1", "bip12", "bip122", "bip122:", + "b", + "bi", + "bip", + "bip1", + "bip12", + "bip122", + "bip122:", // Not an HEX string - "bip122:gg", "bip122::", "bip122:›", "bip122:😁", + "bip122:gg", + "bip122::", + "bip122:›", + "bip122:😁", // Not the expected length - "bip122:a", "bip122:aa", "bip122:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "bip122:a", + "bip122:aa", + "bip122:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", ]; for chain in invalid_chains { - assert!(ChainId::::try_from(chain.as_bytes().to_vec()).is_err(), "Chain ID {:?} should fail to parse for polkadot chains", chain); + assert!( + ChainId::::try_from(chain.as_bytes().to_vec()).is_err(), + "Chain ID {:?} should fail to parse for polkadot chains", + chain + ); } } #[test] fn test_dotsama_chains() { - let valid_chains = ["polkadot:b0a8d493285c2df73290dfb7e61f870f", "polkadot:B0A8D493285C2DF73290DFB7E61F870F", "polkadot:742a2ca70c2fda6cee4f8df98d64c4c6", "polkadot:37e1f8125397a98630013a4dff89b54c", "polkadot:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]; + let valid_chains = [ + "polkadot:b0a8d493285c2df73290dfb7e61f870f", + "polkadot:B0A8D493285C2DF73290DFB7E61F870F", + "polkadot:742a2ca70c2fda6cee4f8df98d64c4c6", + "polkadot:37e1f8125397a98630013a4dff89b54c", + "polkadot:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + ]; for chain in valid_chains { - assert!(ChainId::::try_from(chain.as_bytes().to_vec()).is_ok(), "Chain ID {:?} should not fail to parse for polkadot chains", chain); + assert!( + ChainId::::try_from(chain.as_bytes().to_vec()).is_ok(), + "Chain ID {:?} should not fail to parse for polkadot chains", + chain + ); } let invalid_chains = [ // Too short - "p", "po", "pol", "polk", "polka", "polkad", "polkado", "polkadot", "polkadot:", + "p", + "po", + "pol", + "polk", + "polka", + "polkad", + "polkado", + "polkadot", + "polkadot:", // Not an HEX string - "polkadot:gg", "polkadot::", "polkadot:›", "polkadot:😁", + "polkadot:gg", + "polkadot::", + "polkadot:›", + "polkadot:😁", // Not the expected length - "polkadot:a", "polkadot:aa", "polkadot:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "polkadot:a", + "polkadot:aa", + "polkadot:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", ]; for chain in invalid_chains { - assert!(ChainId::::try_from(chain.as_bytes().to_vec()).is_err(), "Chain ID {:?} should fail to parse for polkadot chains", chain); + assert!( + ChainId::::try_from(chain.as_bytes().to_vec()).is_err(), + "Chain ID {:?} should fail to parse for polkadot chains", + chain + ); } } #[test] fn test_solana_chains() { - let valid_chains = ["solana:a", "solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ", "solana:8E9rvCKLFQia2Y35HXjjpWzj8weVo44K"]; + let valid_chains = [ + "solana:a", + "solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ", + "solana:8E9rvCKLFQia2Y35HXjjpWzj8weVo44K", + ]; for chain in valid_chains { - assert!(ChainId::::try_from(chain.as_bytes().to_vec()).is_ok(), "Chain ID {:?} should not fail to parse for solana chains", chain); + assert!( + ChainId::::try_from(chain.as_bytes().to_vec()).is_ok(), + "Chain ID {:?} should not fail to parse for solana chains", + chain + ); } let invalid_chains = [ // Too short - "s", "so", "sol", "sola", "solan", "solana", "solana:", + "s", + "so", + "sol", + "sola", + "solan", + "solana", + "solana:", // Not a Base58 string - "solana::", "solana:›", "solana:😁", "solana:random-string", + "solana::", + "solana:›", + "solana:😁", + "solana:random-string", // Valid base58 text, too long (34 chars) - "solana:TJ24pxm996UCBScuQRwjYo4wvPjUa8pzKo" + "solana:TJ24pxm996UCBScuQRwjYo4wvPjUa8pzKo", ]; for chain in invalid_chains { - assert!(ChainId::::try_from(chain.as_bytes().to_vec()).is_err(), "Chain ID {:?} should fail to parse for generic chains", chain); + assert!( + ChainId::::try_from(chain.as_bytes().to_vec()).is_err(), + "Chain ID {:?} should fail to parse for generic chains", + chain + ); } } @@ -333,36 +458,66 @@ pub mod chain_id { fn test_generic_chains() { let valid_chains = [ // Edge cases - "abc:-", "-as01-aa:A", "12345678:abcdefghjklmnopqrstuvwxyzABCD012", + "abc:-", + "-as01-aa:A", + "12345678:abcdefghjklmnopqrstuvwxyzABCD012", // Filecoin examples -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-23.md - "fil:t", "fil:f", + "fil:t", + "fil:f", // Tezos examples -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-26.md - "tezos:NetXdQprcVkpaWU", "tezos:NetXm8tYqnMWky1", + "tezos:NetXdQprcVkpaWU", + "tezos:NetXm8tYqnMWky1", // Cosmos examples -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-5.md - "cosmos:cosmoshub-2", "cosmos:cosmoshub-3", "cosmos:Binance-Chain-Tigris", "cosmos:iov-mainnet", "cosmos:x", "cosmos:hash-", "cosmos:hashed", + "cosmos:cosmoshub-2", + "cosmos:cosmoshub-3", + "cosmos:Binance-Chain-Tigris", + "cosmos:iov-mainnet", + "cosmos:x", + "cosmos:hash-", + "cosmos:hashed", // Lisk examples -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-6.md - "lip9:9ee11e9df416b18b", "lip9:e48feb88db5b5cf5", + "lip9:9ee11e9df416b18b", + "lip9:e48feb88db5b5cf5", // EOSIO examples -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-7.md - "eosio:aca376f206b8fc25a6ed44dbdc66547c", "eosio:e70aaab8997e1dfce58fbfac80cbbb8f", "eosio:4667b205c6838ef70ff7988f6e8257e8", "eosio:1eaa0824707c8c16bd25145493bf062a", + "eosio:aca376f206b8fc25a6ed44dbdc66547c", + "eosio:e70aaab8997e1dfce58fbfac80cbbb8f", + "eosio:4667b205c6838ef70ff7988f6e8257e8", + "eosio:1eaa0824707c8c16bd25145493bf062a", // Stellar examples -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-28.md - "stellar:testnet", "stellar:pubnet" + "stellar:testnet", + "stellar:pubnet", ]; for chain in valid_chains { println!("Testing right chain {:?}", chain); - assert!(ChainId::::try_from(chain.as_bytes().to_vec()).is_ok(), "Chain ID {:?} should not fail to parse for generic chains", chain); + assert!( + ChainId::::try_from(chain.as_bytes().to_vec()).is_ok(), + "Chain ID {:?} should not fail to parse for generic chains", + chain + ); } let invalid_chains = [ // Too short - "a", "ab", "01:", "ab-:", + "a", + "ab", + "01:", + "ab-:", // Too long - "123456789:1", "12345678:123456789123456789123456789123456", "123456789:123456789123456789123456789123456", + "123456789:1", + "12345678:123456789123456789123456789123456", + "123456789:123456789123456789123456789123456", // Unallowed characters - "::", "c?1:›", "de:😁", + "::", + "c?1:›", + "de:😁", ]; for chain in invalid_chains { println!("Testing wrong chain {:?}", chain); - assert!(ChainId::::try_from(chain.as_bytes().to_vec()).is_err(), "Chain ID {:?} should fail to parse for solana chains", chain); + assert!( + ChainId::::try_from(chain.as_bytes().to_vec()).is_err(), + "Chain ID {:?} should fail to parse for solana chains", + chain + ); } } diff --git a/pallets/public-credentials/src/credentials.rs b/pallets/public-credentials/src/credentials.rs index 72aacf78c8..87911376c6 100644 --- a/pallets/public-credentials/src/credentials.rs +++ b/pallets/public-credentials/src/credentials.rs @@ -39,7 +39,15 @@ pub struct ClaimerSignatureInfo { } #[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, PartialOrd, Ord, TypeInfo)] -pub struct Credential { +pub struct Credential< + CtypeHash, + SubjectIdentifier, + ClaimContent, + ClaimHash, + Nonce, + ClaimerSignature, + AuthorizationControl, +> { pub claim: Claim, pub nonce: Nonce, pub claim_hash: ClaimHash, diff --git a/pallets/public-credentials/src/lib.rs b/pallets/public-credentials/src/lib.rs index a7adbf3ef0..981b9cd651 100644 --- a/pallets/public-credentials/src/lib.rs +++ b/pallets/public-credentials/src/lib.rs @@ -63,8 +63,9 @@ pub mod pallet { pub(crate) type AccountIdOf = attestation::AccountIdOf; pub(crate) type BalanceOf = <::Currency as Currency>>::Balance; pub(crate) type BlockNumberOf = ::BlockNumber; - // No easy way to check whether the two currencies are the same and check for `can_withdraw` conditions. - // Maybe with #[transactional] we could stop caring and simply rollback if the two are the same and there is not enough + // No easy way to check whether the two currencies are the same and check for + // `can_withdraw` conditions. Maybe with #[transactional] we could stop caring + // and simply rollback if the two are the same and there is not enough // for both operations. pub(crate) type CurrencyOf = ::Currency; pub(crate) type SubjectIdOf = ::SubjectId; @@ -77,7 +78,7 @@ pub mod pallet { ClaimHashOf, H256, ClaimerSignatureInfo<::ClaimerIdentifier, ::ClaimerSignature>, - ::AccessControl + ::AccessControl, >; #[pallet::config] @@ -118,8 +119,8 @@ pub mod pallet { // Reverse map to make sure that the same claim hash cannot be issued to two // different subjects by issuing it to subject #1, then removing it only from // the attestation pallet and then issuing it to subject #2. - // This map ensures that at any time a claim hash is only linked (i.e., issued) to a single - // subject. + // This map ensures that at any time a claim hash is only linked (i.e., issued) + // to a single subject. // Not exposed to the outside world. #[pallet::storage] #[pallet::getter(fn attested_claim_hashes)] @@ -186,9 +187,10 @@ pub mod pallet { // Check that enough funds can be reserved to pay for both attestation and // public info deposits. - // It is harder to use two potentially different currencies while making sure that, if the same, the sum can be reserved, - // but if they are not, then each deposit could be reserved separately. - // We could switch to using `NamedReservableCurrency` to do that. + // It is harder to use two potentially different currencies while making sure + // that, if the same, the sum can be reserved, but if they are not, then each + // deposit could be reserved separately. We could switch to using + // `NamedReservableCurrency` to do that. ensure!( as ReservableCurrency>>::can_reserve( &payer, @@ -212,13 +214,20 @@ pub mod pallet { // Delegate to the attestation pallet writing the attestation information and // reserve its part of the deposit - attestation::Pallet::::write_attestation(ctype_hash, claim_hash, attester, payer.clone(), authorization_info)?; + attestation::Pallet::::write_attestation( + ctype_hash, + claim_hash, + attester, + payer.clone(), + authorization_info, + )?; // *** No Fail beyond this point *** // Take the rest of the deposit. Should never fail since we made sure above that // enough funds can be reserved. - let deposit = kilt_support::reserve_deposit::, CurrencyOf>(payer, deposit_amount).map_err(|_| Error::::InternalError)?; + let deposit = kilt_support::reserve_deposit::, CurrencyOf>(payer, deposit_amount) + .map_err(|_| Error::::InternalError)?; let block_number = frame_system::Pallet::::block_number(); @@ -235,7 +244,11 @@ pub mod pallet { } #[pallet::weight(0)] - pub fn remove(origin: OriginFor, claim_hash: ClaimHashOf, authorization: Option) -> DispatchResultWithPostInfo { + pub fn remove( + origin: OriginFor, + claim_hash: ClaimHashOf, + authorization: Option, + ) -> DispatchResultWithPostInfo { let source = ::EnsureOrigin::ensure_origin(origin)?; let attester = source.subject(); diff --git a/pallets/public-credentials/src/mock.rs b/pallets/public-credentials/src/mock.rs index 6816909aa4..419813c140 100644 --- a/pallets/public-credentials/src/mock.rs +++ b/pallets/public-credentials/src/mock.rs @@ -23,8 +23,8 @@ use ctype::CtypeHashOf; use kilt_support::deposit::Deposit; use crate::{ - AccountIdOf, BalanceOf, Claim, CurrencyOf, ClaimerSignatureInfo, Config, CredentialEntryOf, CredentialOf, Credentials, - CredentialsUnicityIndex, SubjectIdOf, + AccountIdOf, BalanceOf, Claim, ClaimerSignatureInfo, Config, CredentialEntryOf, CredentialOf, Credentials, + CredentialsUnicityIndex, CurrencyOf, SubjectIdOf, }; pub(crate) type BlockNumber = u64; @@ -58,8 +58,11 @@ pub fn insert_public_credentials( claim_hash: ClaimHashOf, credential_entry: CredentialEntryOf, ) { - kilt_support::reserve_deposit::, CurrencyOf>(credential_entry.deposit.owner.clone(), credential_entry.deposit.amount) - .expect("Attester should have enough balance"); + kilt_support::reserve_deposit::, CurrencyOf>( + credential_entry.deposit.owner.clone(), + credential_entry.deposit.amount, + ) + .expect("Attester should have enough balance"); Credentials::::insert(&subject_id, &claim_hash, credential_entry); CredentialsUnicityIndex::::insert(claim_hash, subject_id); @@ -86,14 +89,14 @@ pub use crate::mock::runtime::*; pub(crate) mod runtime { use super::*; - use codec::{Encode, Decode, MaxEncodedLen}; - use scale_info::TypeInfo; + use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ parameter_types, traits::{ConstU128, ConstU16, ConstU32, ConstU64}, weights::constants::RocksDbWeight, }; - use sp_core::{sr25519, Pair,}; + use scale_info::TypeInfo; + use sp_core::{sr25519, Pair}; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, @@ -114,7 +117,19 @@ pub(crate) mod runtime { pub type AccountPublic = ::Signer; pub type AccountId = ::AccountId; - #[derive(Default, Encode, Clone, Decode, MaxEncodedLen, sp_runtime::RuntimeDebug, Eq, PartialEq, Ord, PartialOrd, TypeInfo)] + #[derive( + Default, + Encode, + Clone, + Decode, + MaxEncodedLen, + sp_runtime::RuntimeDebug, + Eq, + PartialEq, + Ord, + PartialOrd, + TypeInfo, + )] pub struct TestSubjectId([u8; 32]); impl core::ops::Deref for TestSubjectId { diff --git a/pallets/public-credentials/src/tests.rs b/pallets/public-credentials/src/tests.rs index c58c1ad3b8..a063d30523 100644 --- a/pallets/public-credentials/src/tests.rs +++ b/pallets/public-credentials/src/tests.rs @@ -58,8 +58,9 @@ fn add_with_no_signature_successful() { )); let stored_attestation = Attestations::::get(&claim_hash).expect("Attestation should be present on chain."); - let stored_public_credential_details = Credentials::::get(&TestSubjectId::try_from(subject_id.clone()).unwrap(), &claim_hash) - .expect("Public credential details should be present on chain."); + let stored_public_credential_details = + Credentials::::get(&TestSubjectId::try_from(subject_id.clone()).unwrap(), &claim_hash) + .expect("Public credential details should be present on chain."); // Test interactions with attestation pallet assert_eq!(stored_attestation.ctype_hash, ctype_hash); @@ -73,7 +74,10 @@ fn add_with_no_signature_successful() { ); // Check deposit reservation logic - assert_eq!(Balances::reserved_balance(ACCOUNT_00), public_credential_deposit + attestation_deposit); + assert_eq!( + Balances::reserved_balance(ACCOUNT_00), + public_credential_deposit + attestation_deposit + ); // Re-issuing the same credential will fail assert_noop!( @@ -85,7 +89,10 @@ fn add_with_no_signature_successful() { ); // Check deposit has not changed - assert_eq!(Balances::reserved_balance(ACCOUNT_00), public_credential_deposit + attestation_deposit); + assert_eq!( + Balances::reserved_balance(ACCOUNT_00), + public_credential_deposit + attestation_deposit + ); System::set_block_number(1); @@ -97,8 +104,9 @@ fn add_with_no_signature_successful() { let stored_attestation = Attestations::::get(&claim_hash_2).expect("Attestation #2 should be present on chain."); - let stored_public_credential_details = Credentials::::get(&TestSubjectId::try_from(subject_id.clone()).unwrap(), &claim_hash_2) - .expect("Public credential #2 details should be present on chain."); + let stored_public_credential_details = + Credentials::::get(&TestSubjectId::try_from(subject_id.clone()).unwrap(), &claim_hash_2) + .expect("Public credential #2 details should be present on chain."); // Test interactions with attestation pallet assert_eq!(stored_attestation.ctype_hash, ctype_hash); @@ -106,10 +114,16 @@ fn add_with_no_signature_successful() { // Test this pallet logic assert_eq!(stored_public_credential_details.block_number, 1); - assert_eq!(CredentialsUnicityIndex::::get(&claim_hash_2), Some(subject_id.into())); + assert_eq!( + CredentialsUnicityIndex::::get(&claim_hash_2), + Some(subject_id.into()) + ); // Deposit is 2x now - assert_eq!(Balances::reserved_balance(ACCOUNT_00), 2 * (public_credential_deposit + attestation_deposit)); + assert_eq!( + Balances::reserved_balance(ACCOUNT_00), + 2 * (public_credential_deposit + attestation_deposit) + ); // Deleting the attestation only from the attestation pallet will still fail Attestation::reclaim_deposit(Origin::signed(ACCOUNT_00), claim_hash) @@ -123,7 +137,10 @@ fn add_with_no_signature_successful() { ); // Deposit should now be equal to 1 attestation + 2 public credentials - assert_eq!(Balances::reserved_balance(ACCOUNT_00), 2 * public_credential_deposit + attestation_deposit); + assert_eq!( + Balances::reserved_balance(ACCOUNT_00), + 2 * public_credential_deposit + attestation_deposit + ); }); } @@ -159,8 +176,9 @@ fn add_with_claimer_signature_successful() { )); let stored_attestation = Attestations::::get(&claim_hash).expect("Attestation should be present on chain."); - let stored_public_credential_details = Credentials::::get(&TestSubjectId::try_from(subject_id.clone()).unwrap(), &claim_hash) - .expect("Public credential details should be present on chain."); + let stored_public_credential_details = + Credentials::::get(&TestSubjectId::try_from(subject_id.clone()).unwrap(), &claim_hash) + .expect("Public credential details should be present on chain."); // Test interactions with attestation pallet assert_eq!(stored_attestation.ctype_hash, ctype_hash); @@ -168,7 +186,10 @@ fn add_with_claimer_signature_successful() { // Test this pallet logic assert_eq!(stored_public_credential_details.block_number, System::block_number()); - assert_eq!(CredentialsUnicityIndex::::get(&claim_hash), Some(subject_id.try_into().unwrap())); + assert_eq!( + CredentialsUnicityIndex::::get(&claim_hash), + Some(subject_id.try_into().unwrap()) + ); }); } @@ -178,7 +199,8 @@ fn add_not_enough_balance() { let subject_id = SUBJECT_ID_00; let claim_hash = claim_hash_from_seed(CLAIM_HASH_SEED_01); let ctype_hash = get_ctype_hash::(true); - let new_credential = generate_base_public_credential_creation_op::(subject_id.into(), claim_hash, ctype_hash, None); + let new_credential = + generate_base_public_credential_creation_op::(subject_id.into(), claim_hash, ctype_hash, None); let public_credential_deposit = ::Deposit::get(); let attestation_deposit = ::Deposit::get(); @@ -303,7 +325,9 @@ fn add_invalid_subject_id() { #[test] fn remove_successful() { let attester = sr25519_did_from_seed(&ALICE_SEED); - let subject_id: SubjectIdOf = SUBJECT_ID_00.try_into().expect("Should not fail to parse test SubjectId."); + let subject_id: SubjectIdOf = SUBJECT_ID_00 + .try_into() + .expect("Should not fail to parse test SubjectId."); let claim_hash = claim_hash_from_seed(CLAIM_HASH_SEED_01); let attestation: AttestationDetails = generate_base_attestation(attester.clone(), ACCOUNT_00); let new_credential = generate_base_credential_entry(ACCOUNT_00, 0); @@ -326,7 +350,10 @@ fn remove_successful() { assert_eq!(Attestations::::get(&claim_hash), None); // Test this pallet logic - assert_eq!(Credentials::::get(&TestSubjectId::try_from(subject_id.clone()).unwrap(), &claim_hash), None); + assert_eq!( + Credentials::::get(&TestSubjectId::try_from(subject_id.clone()).unwrap(), &claim_hash), + None + ); assert_eq!(CredentialsUnicityIndex::::get(&claim_hash), None); // Check deposit release logic @@ -362,7 +389,9 @@ fn remove_successful() { fn remove_unauthorized() { let attester = sr25519_did_from_seed(&ALICE_SEED); let wrong_attester = sr25519_did_from_seed(&BOB_SEED); - let subject_id: SubjectIdOf = SUBJECT_ID_00.try_into().expect("Should not fail to parse test SubjectId."); + let subject_id: SubjectIdOf = SUBJECT_ID_00 + .try_into() + .expect("Should not fail to parse test SubjectId."); let claim_hash = claim_hash_from_seed(CLAIM_HASH_SEED_01); let attestation: AttestationDetails = generate_base_attestation(attester.clone(), ACCOUNT_00); let new_credential = generate_base_credential_entry(ACCOUNT_00, 0); @@ -388,7 +417,9 @@ fn remove_unauthorized() { #[test] fn reclaim_deposit_successful() { let attester = sr25519_did_from_seed(&ALICE_SEED); - let subject_id: SubjectIdOf = SUBJECT_ID_00.try_into().expect("Should not fail to parse test SubjectId."); + let subject_id: SubjectIdOf = SUBJECT_ID_00 + .try_into() + .expect("Should not fail to parse test SubjectId."); let claim_hash = claim_hash_from_seed(CLAIM_HASH_SEED_01); let attestation: AttestationDetails = generate_base_attestation(attester.clone(), ACCOUNT_00); let new_credential = generate_base_credential_entry(ACCOUNT_00, 0); @@ -410,7 +441,10 @@ fn reclaim_deposit_successful() { assert_eq!(Attestations::::get(&claim_hash), None); // Test this pallet logic - assert_eq!(Credentials::::get(&TestSubjectId::try_from(subject_id.clone()).unwrap(), &claim_hash), None); + assert_eq!( + Credentials::::get(&TestSubjectId::try_from(subject_id.clone()).unwrap(), &claim_hash), + None + ); assert_eq!(CredentialsUnicityIndex::::get(&claim_hash), None); // Check deposit release logic @@ -445,7 +479,9 @@ fn reclaim_deposit_successful() { #[test] fn reclaim_deposit_unauthorized() { let attester = sr25519_did_from_seed(&ALICE_SEED); - let subject_id: SubjectIdOf = SUBJECT_ID_00.try_into().expect("Should not fail to parse test SubjectId."); + let subject_id: SubjectIdOf = SUBJECT_ID_00 + .try_into() + .expect("Should not fail to parse test SubjectId."); let claim_hash = claim_hash_from_seed(CLAIM_HASH_SEED_01); let attestation: AttestationDetails = generate_base_attestation(attester.clone(), ACCOUNT_00); let new_credential = generate_base_credential_entry(ACCOUNT_00, 0); diff --git a/support/src/deposit.rs b/support/src/deposit.rs index 6479bf3051..c7abae2f94 100644 --- a/support/src/deposit.rs +++ b/support/src/deposit.rs @@ -18,7 +18,7 @@ use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::traits::ReservableCurrency; use scale_info::TypeInfo; -use sp_runtime::{DispatchError, traits::Zero}; +use sp_runtime::{traits::Zero, DispatchError}; /// An amount of balance reserved by the specified address. #[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, TypeInfo, MaxEncodedLen)] @@ -30,10 +30,7 @@ pub struct Deposit { pub fn reserve_deposit>( account: Account, deposit_amount: Currency::Balance, -) -> Result< - Deposit, - DispatchError> -{ +) -> Result, DispatchError> { Currency::reserve(&account, deposit_amount)?; Ok(Deposit { owner: account, From f9878c9c4b8588b4f0090b6bec00433a767a7b49 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Thu, 16 Jun 2022 12:06:32 +0200 Subject: [PATCH 017/138] fix: uncomment remaining test functions --- pallets/public-credentials/src/assets.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pallets/public-credentials/src/assets.rs b/pallets/public-credentials/src/assets.rs index 0543ccd8a6..1583ee3481 100644 --- a/pallets/public-credentials/src/assets.rs +++ b/pallets/public-credentials/src/assets.rs @@ -524,13 +524,13 @@ pub mod chain_id { #[test] fn test_utility_functions() { // These functions should never crash. We just check that here. - // ChainId::::ethereum_mainnet(); - // ChainId::::moonbeam_eth(); + ChainId::::ethereum_mainnet(); + ChainId::::moonbeam_eth(); ChainId::::bitcoin_mainnet(); - // ChainId::::polkadot(); - // ChainId::::kusama(); - // ChainId::::kilt_spiritnet(); - // ChainId::::solana_mainnet(); + ChainId::::polkadot(); + ChainId::::kusama(); + ChainId::::kilt_spiritnet(); + ChainId::::solana_mainnet(); } } } From 18c7326e439bd8e6b89699cbeb182703bd8db554 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Thu, 16 Jun 2022 14:21:53 +0200 Subject: [PATCH 018/138] wip: first version of new pallet --- Cargo.lock | 7 +++ Cargo.toml | 1 + pallets/public-credentials/src/assets.rs | 72 ++++++++++++++---------- 3 files changed, 50 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8f24e7457d..48b29ee13d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3531,6 +3531,13 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" +[[package]] +name = "kilt-caip-assets" +version = "1.6.2" +dependencies = [ + "frame-support", +] + [[package]] name = "kilt-parachain" version = "1.6.2" diff --git a/Cargo.toml b/Cargo.toml index 89ae7cf8b4..f4a720340d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,5 @@ members = [ "pallets/*", "runtimes/*", "support", + "utilities/*", ] diff --git a/pallets/public-credentials/src/assets.rs b/pallets/public-credentials/src/assets.rs index 1583ee3481..e7cd0135c9 100644 --- a/pallets/public-credentials/src/assets.rs +++ b/pallets/public-credentials/src/assets.rs @@ -31,7 +31,7 @@ pub mod chain_id { const MINIMUM_REFERENCE_LENGTH: u32 = 1; const MAXIMUM_REFERENCE_LENGTH: u32 = 32; - #[derive(std::fmt::Debug, PartialEq, Eq, PartialOrd, Ord)] + #[derive(sp_runtime::RuntimeDebug, PartialEq, Eq, PartialOrd, Ord)] pub enum ChainId { Eip155(Eip155Reference), Bip122(GenesisHexHashReference), @@ -235,40 +235,38 @@ pub mod chain_id { ); let mut components = value.split(|c| *c == b':'); - if let (Some(namespace), Some(reference)) = (components.next(), components.next()) { - let namespace_length = namespace.iter().try_fold(0u32, |length, c| { - let new_length = length + 1; - if new_length > MAXIMUM_NAMESPACE_LENGTH { - return Err(Error::::InvalidInput); - } - if !matches!(c, b'-' | b'a'..=b'z' | b'0'..=b'9') { - return Err(Error::::InvalidInput); - } - Ok(new_length) - })?; - if namespace_length < MINIMUM_NAMESPACE_LENGTH { + let (namespace, reference) = (components.next().ok_or(Error::::InvalidInput)?, components.next().ok_or(Error::::InvalidInput)?); + let namespace_length = namespace.iter().try_fold(0u32, |length, c| { + let new_length = length + 1; + if new_length > MAXIMUM_NAMESPACE_LENGTH { + return Err(Error::::InvalidInput); + } + if !matches!(c, b'-' | b'a'..=b'z' | b'0'..=b'9') { return Err(Error::::InvalidInput); } + Ok(new_length) + })?; + if namespace_length < MINIMUM_NAMESPACE_LENGTH { + return Err(Error::::InvalidInput); + } - let reference_length = reference.iter().try_fold(0u32, |length, c| { - let new_length = length + 1; - if new_length > MAXIMUM_REFERENCE_LENGTH { - return Err(Error::::InvalidInput); - } - if !matches!(c, b'-' | b'a'..=b'z' | b'A'..=b'Z' |b'0'..=b'9') { - return Err(Error::::InvalidInput); - } - Ok(new_length) - })?; - if reference_length < MINIMUM_REFERENCE_LENGTH { + let reference_length = reference.iter().try_fold(0u32, |length, c| { + let new_length = length + 1; + if new_length > MAXIMUM_REFERENCE_LENGTH { return Err(Error::::InvalidInput); } - Ok(Self::from_components( - namespace.to_vec().try_into().unwrap(), - reference.to_vec().try_into().unwrap(), - )) - } else { - Err(Error::::InvalidInput) + if !matches!(c, b'-' | b'a'..=b'z' | b'A'..=b'Z' |b'0'..=b'9') { + return Err(Error::::InvalidInput); + } + Ok(new_length) + })?; + if reference_length < MINIMUM_REFERENCE_LENGTH { + return Err(Error::::InvalidInput); + } + Ok(Self::from_components( + namespace.to_vec().try_into().unwrap(), + reference.to_vec().try_into().unwrap(), + )) } } } @@ -534,3 +532,17 @@ pub mod chain_id { } } } + +pub mod asset_id { + use super::chain_id::*; + + const MINIMUM_NAMESPACE_LENGTH: u32 = 3; + const MAXIMUM_NAMESPACE_LENGTH: u32 = 8; + const MINIMUM_REFERENCE_LENGTH: u32 = 1; + const MAXIMUM_REFERENCE_LENGTH: u32 = 64; + + #[derive(sp_runtime::RuntimeDebug, PartialEq, Eq, PartialOrd, Ord)] + pub enum AssetId { + + } +} From eed971900bed94ac0a385cc37fb30037bb17bdda Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Thu, 16 Jun 2022 16:27:25 +0200 Subject: [PATCH 019/138] feat: new pallet for chain IDs --- Cargo.lock | 1 + utilities/assets/Cargo.toml | 18 ++ utilities/assets/src/chain_id.rs | 340 +++++++++++++++++++++++++++++++ utilities/assets/src/lib.rs | 21 ++ 4 files changed, 380 insertions(+) create mode 100644 utilities/assets/Cargo.toml create mode 100644 utilities/assets/src/chain_id.rs create mode 100644 utilities/assets/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 48b29ee13d..01db749705 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3535,6 +3535,7 @@ checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" name = "kilt-caip-assets" version = "1.6.2" dependencies = [ + "base58", "frame-support", ] diff --git a/utilities/assets/Cargo.toml b/utilities/assets/Cargo.toml new file mode 100644 index 0000000000..dcbdc33ea4 --- /dev/null +++ b/utilities/assets/Cargo.toml @@ -0,0 +1,18 @@ +[package] +authors = ["KILT "] +description = "Asset DIDs and related structs, suitable for no_std environments." +edition = "2021" +name = "kilt-caip-assets" +repository = "https://github.com/KILTprotocol/mashnet-node" +version = "1.6.2" + +[dependencies] +frame-support = {branch = "polkadot-v0.9.23", default-features = false, git = "https://github.com/paritytech/substrate"} +base58 = {version = "0.2.0", default-features = false} + +[features] +default = ["std"] + +std = [ + "frame-support/std", +] diff --git a/utilities/assets/src/chain_id.rs b/utilities/assets/src/chain_id.rs new file mode 100644 index 0000000000..ff1bcf482d --- /dev/null +++ b/utilities/assets/src/chain_id.rs @@ -0,0 +1,340 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2022 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use base58::FromBase58; +use core::str; + +use frame_support::{sp_runtime::traits::CheckedConversion, traits::ConstU32, BoundedVec}; + +const MINIMUM_NAMESPACE_LENGTH: u32 = 3; +const MAXIMUM_NAMESPACE_LENGTH: u32 = 8; +const MINIMUM_REFERENCE_LENGTH: u32 = 1; +const MAXIMUM_REFERENCE_LENGTH: u32 = 32; + +pub enum ChainId { + Eip155(Eip155Reference), + Bip122(GenesisHexHashReference), + Dotsama(GenesisHexHashReference), + Solana(GenesisBase58HashReference), + Generic(GenericChainId), +} + +pub enum ChainIdError { + Namespace(NamespaceError), + Reference(ReferenceError), + InvalidFormat, +} + +pub enum NamespaceError { + TooLong, + TooShort, + InvalidCharacter, +} + +pub enum ReferenceError { + TooLong, + TooShort, + InvalidCharacter, +} + +impl TryFrom<&[u8]> for ChainId { + type Error = ChainIdError; + + fn try_from(value: &[u8]) -> Result { + match value { + // "eip155:" chains -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-3.md + [b'e', b'i', b'p', b'1', b'5', b'5', b':', chain_id @ ..] => { + Eip155Reference::try_from(chain_id).map(Self::Eip155) + } + // "bip122:" chains -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-4.md + [b'b', b'i', b'p', b'1', b'2', b'2', b':', chain_id @ ..] => { + GenesisHexHashReference::try_from(chain_id).map(Self::Bip122) + } + // "polkadot:" chains -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-13.md + [b'p', b'o', b'l', b'k', b'a', b'd', b'o', b't', b':', chain_id @ ..] => { + GenesisHexHashReference::try_from(chain_id).map(Self::Dotsama) + } + // "solana:" chains -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-30.md + [b's', b'o', b'l', b'a', b'n', b'a', b':', chain_id @ ..] => { + GenesisBase58HashReference::try_from(chain_id).map(Self::Solana) + } + // Other chains that are still compatible with the CAIP-2 spec -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md + chain_id => GenericChainId::try_from(chain_id).map(Self::Generic), + } + } +} + +impl ChainId { + pub fn ethereum_mainnet() -> Self { + Self::Eip155(Eip155Reference::from_slice_unchecked(b"1")) + } + + pub fn moonriver_eth() -> Self { + // Info taken from https://chainlist.org/ + Self::Eip155(Eip155Reference::from_slice_unchecked(b"1285")) + } + + pub fn moonbeam_eth() -> Self { + // Info taken from https://chainlist.org/ + Self::Eip155(Eip155Reference::from_slice_unchecked(b"1284")) + } + + pub fn bitcoin_mainnet() -> Self { + Self::Bip122(GenesisHexHashReference::from_slice_unchecked( + b"000000000019d6689c085ae165831e93", + )) + } + + pub fn polkadot() -> Self { + Self::Dotsama(GenesisHexHashReference::from_slice_unchecked( + b"91b171bb158e2d3848fa23a9f1c25182", + )) + } + + pub fn kusama() -> Self { + Self::Dotsama(GenesisHexHashReference::from_slice_unchecked( + b"b0a8d493285c2df73290dfb7e61f870f", + )) + } + + pub fn kilt_spiritnet() -> Self { + Self::Dotsama(GenesisHexHashReference::from_slice_unchecked( + b"411f057b9107718c9624d6aa4a3f23c1", + )) + } + + pub fn solana_mainnet() -> Self { + Self::Solana(GenesisBase58HashReference::from_slice_unchecked( + b"4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ", + )) + } +} + +pub struct Eip155Reference(BoundedVec>); + +impl Eip155Reference { + #[allow(dead_code)] + pub(crate) fn from_slice_unchecked(slice: &[u8]) -> Self { + Self(slice.to_vec().try_into().unwrap()) + } +} + +impl TryFrom<&[u8]> for Eip155Reference { + type Error = ChainIdError; + + fn try_from(value: &[u8]) -> Result { + let input_length = value + .len() + .checked_into::() + .ok_or(ChainIdError::Reference(ReferenceError::TooLong))?; + if input_length < MINIMUM_REFERENCE_LENGTH { + Err(ChainIdError::Reference(ReferenceError::TooShort)) + } else if input_length > MAXIMUM_REFERENCE_LENGTH { + Err(ChainIdError::Reference(ReferenceError::TooLong)) + } else { + value.iter().try_for_each(|c| { + if !(b'0'..=b'9').contains(c) { + Err(ChainIdError::Reference(ReferenceError::InvalidCharacter)) + } else { + Ok(()) + } + })?; + // Unchecked since we already checked for length + Ok(Self::from_slice_unchecked(value)) + } + } +} + +pub struct GenesisHexHashReference(BoundedVec>); + +impl GenesisHexHashReference { + #[allow(dead_code)] + pub(crate) fn from_slice_unchecked(slice: &[u8]) -> Self { + Self(slice.to_vec().try_into().unwrap()) + } +} + +impl TryFrom<&[u8]> for GenesisHexHashReference { + type Error = ChainIdError; + + fn try_from(value: &[u8]) -> Result { + let input_length = value + .len() + .checked_into::() + .ok_or(ChainIdError::Reference(ReferenceError::TooLong))?; + if input_length < MINIMUM_REFERENCE_LENGTH { + Err(ChainIdError::Reference(ReferenceError::TooShort)) + } else if input_length > MAXIMUM_REFERENCE_LENGTH { + Err(ChainIdError::Reference(ReferenceError::TooLong)) + } else if input_length % 2 != 0 { + // Hex encoding can only have 2x characters + Err(ChainIdError::InvalidFormat) + } else { + value.iter().try_for_each(|c| { + if !matches!(c, b'0'..=b'9' | b'a'..=b'f') { + Err(ChainIdError::Reference(ReferenceError::InvalidCharacter)) + } else { + Ok(()) + } + })?; + // Unchecked since we already checked for length + Ok(Self::from_slice_unchecked(value)) + } + } +} + +pub struct GenesisBase58HashReference(BoundedVec>); + +impl GenesisBase58HashReference { + #[allow(dead_code)] + pub(crate) fn from_slice_unchecked(slice: &[u8]) -> Self { + Self(slice.to_vec().try_into().unwrap()) + } +} + +impl TryFrom<&[u8]> for GenesisBase58HashReference { + type Error = ChainIdError; + + fn try_from(value: &[u8]) -> Result { + let input_length = value + .len() + .checked_into::() + .ok_or(ChainIdError::Reference(ReferenceError::TooLong))?; + if input_length < MINIMUM_REFERENCE_LENGTH { + Err(ChainIdError::Reference(ReferenceError::TooShort)) + } else if input_length > MAXIMUM_REFERENCE_LENGTH { + Err(ChainIdError::Reference(ReferenceError::TooLong)) + } else { + let decoded_string = + str::from_utf8(value).map_err(|_| ChainIdError::Reference(ReferenceError::InvalidCharacter))?; + // Check for proper base58 encoding + decoded_string + .from_base58() + .map_err(|_| ChainIdError::Reference(ReferenceError::InvalidCharacter))?; + // Unchecked since we already checked for length + Ok(Self::from_slice_unchecked(value)) + } + } +} + +pub struct GenericChainId { + pub namespace: ChainNamespace, + pub reference: ChainReference, +} + +impl GenericChainId { + #[allow(dead_code)] + fn from_raw_unchecked(namespace: &[u8], reference: &[u8]) -> Self { + Self { + namespace: ChainNamespace::from_slice_unchecked(namespace), + reference: ChainReference::from_slice_unchecked(reference), + } + } +} + +impl TryFrom<&[u8]> for GenericChainId { + type Error = ChainIdError; + + fn try_from(value: &[u8]) -> Result { + let input_length = value.len().checked_into::().ok_or(ChainIdError::InvalidFormat)?; + if input_length > MINIMUM_NAMESPACE_LENGTH + MAXIMUM_NAMESPACE_LENGTH + 1 { + return Err(ChainIdError::InvalidFormat); + } + + let mut components = value.split(|c| *c == b':'); + + let namespace = components + .next() + .ok_or(ChainIdError::InvalidFormat) + .and_then(ChainNamespace::try_from)?; + let reference = components + .next() + .ok_or(ChainIdError::InvalidFormat) + .and_then(ChainReference::try_from)?; + + Ok(Self { namespace, reference }) + } +} + +pub struct ChainNamespace(BoundedVec>); + +impl ChainNamespace { + fn from_slice_unchecked(value: &[u8]) -> Self { + Self(value.to_vec().try_into().unwrap()) + } +} + +impl TryFrom<&[u8]> for ChainNamespace { + type Error = ChainIdError; + + fn try_from(value: &[u8]) -> Result { + let input_length = value + .len() + .checked_into::() + .ok_or(ChainIdError::Namespace(NamespaceError::TooLong))?; + if input_length < MINIMUM_NAMESPACE_LENGTH { + Err(ChainIdError::Namespace(NamespaceError::TooShort)) + } else if input_length > MAXIMUM_NAMESPACE_LENGTH { + Err(ChainIdError::Namespace(NamespaceError::TooLong)) + } else { + value.iter().try_for_each(|c| { + if !matches!(c, b'-' | b'a'..=b'z' | b'0'..=b'9') { + Err(ChainIdError::Namespace(NamespaceError::InvalidCharacter)) + } else { + Ok(()) + } + })?; + // Unchecked since we already checked for length + Ok(Self::from_slice_unchecked(value)) + } + } +} + +pub struct ChainReference(BoundedVec>); + +impl ChainReference { + fn from_slice_unchecked(value: &[u8]) -> Self { + Self(value.to_vec().try_into().unwrap()) + } +} + +impl TryFrom<&[u8]> for ChainReference { + type Error = ChainIdError; + + fn try_from(value: &[u8]) -> Result { + let input_length = value + .len() + .checked_into::() + .ok_or(ChainIdError::Reference(ReferenceError::TooLong))?; + if input_length < MINIMUM_REFERENCE_LENGTH { + Err(ChainIdError::Reference(ReferenceError::TooShort)) + } else if input_length > MAXIMUM_REFERENCE_LENGTH { + Err(ChainIdError::Reference(ReferenceError::TooLong)) + } else { + value.iter().try_for_each(|c| { + if !matches!(c, b'-' | b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9') { + Err(ChainIdError::Reference(ReferenceError::InvalidCharacter)) + } else { + Ok(()) + } + })?; + // Unchecked since we already checked for length + Ok(Self::from_slice_unchecked(value)) + } + } +} diff --git a/utilities/assets/src/lib.rs b/utilities/assets/src/lib.rs new file mode 100644 index 0000000000..a8234a7b81 --- /dev/null +++ b/utilities/assets/src/lib.rs @@ -0,0 +1,21 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2022 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod chain_id; From 918fff02766b7af8a48b0540eed9dcde374bbdef Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Thu, 16 Jun 2022 16:32:31 +0200 Subject: [PATCH 020/138] wip: fixing last issues --- utilities/assets/src/chain_id.rs | 259 +++++++++++++++++++++++++++++++ 1 file changed, 259 insertions(+) diff --git a/utilities/assets/src/chain_id.rs b/utilities/assets/src/chain_id.rs index ff1bcf482d..73985b336c 100644 --- a/utilities/assets/src/chain_id.rs +++ b/utilities/assets/src/chain_id.rs @@ -169,6 +169,7 @@ impl GenesisHexHashReference { } } +// FIXME: Ensure that a size is given for the expected hash length (less than the max allowed size). impl TryFrom<&[u8]> for GenesisHexHashReference { type Error = ChainIdError; @@ -198,6 +199,7 @@ impl TryFrom<&[u8]> for GenesisHexHashReference { } } +// FIXME: Ensure that a size is given for the expected hash length (less than the max allowed size). pub struct GenesisBase58HashReference(BoundedVec>); impl GenesisBase58HashReference { @@ -338,3 +340,260 @@ impl TryFrom<&[u8]> for ChainReference { } } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_eip155_chains() { + let valid_chains = [ + "eip155:1", + "eip155:5", + "eip155:99999999999999999999999999999999", + "eip155:0", + ]; + for chain in valid_chains { + assert!( + ChainId::try_from(chain.as_bytes()).is_ok(), + "Chain ID {:?} should not fail to parse for eip155 chains", + chain + ); + } + + let invalid_chains = [ + // Too short + "e", + "ei", + "eip", + "eip1", + "eip15", + "eip155", + "eip155:", + // Not a number + "eip155:a", + "eip155::", + "eip155:›", + "eip155:😁", + // Max chars + 1 + "eip155:999999999999999999999999999999999", + ]; + for chain in invalid_chains { + assert!( + ChainId::try_from(chain.as_bytes()).is_err(), + "Chain ID {:?} should fail to parse for eip155 chains", + chain + ); + } + } + + #[test] + fn test_bip122_chains() { + let valid_chains = [ + "bip122:000000000019d6689c085ae165831e93", + "bip122:12a765e31ffd4059bada1e25190f6e98", + "bip122:fdbe99b90c90bae7505796461471d89a", + "bip122:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + ]; + for chain in valid_chains { + assert!( + ChainId::try_from(chain.as_bytes()).is_ok(), + "Chain ID {:?} should not fail to parse for polkadot chains", + chain + ); + } + + let invalid_chains = [ + // Too short + "b", + "bi", + "bip", + "bip1", + "bip12", + "bip122", + "bip122:", + // Not an HEX string + "bip122:gg", + "bip122::", + "bip122:›", + "bip122:😁", + // Not the expected length + "bip122:a", + "bip122:aa", + "bip122:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + ]; + for chain in invalid_chains { + assert!( + ChainId::try_from(chain.as_bytes()).is_err(), + "Chain ID {:?} should fail to parse for polkadot chains", + chain + ); + } + } + + #[test] + fn test_dotsama_chains() { + let valid_chains = [ + "polkadot:b0a8d493285c2df73290dfb7e61f870f", + "polkadot:742a2ca70c2fda6cee4f8df98d64c4c6", + "polkadot:37e1f8125397a98630013a4dff89b54c", + "polkadot:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + ]; + for chain in valid_chains { + assert!( + ChainId::try_from(chain.as_bytes()).is_ok(), + "Chain ID {:?} should not fail to parse for polkadot chains", + chain + ); + } + + let invalid_chains = [ + // Too short + "p", + "po", + "pol", + "polk", + "polka", + "polkad", + "polkado", + "polkadot", + "polkadot:", + // Not an HEX string + "polkadot:gg", + "polkadot::", + "polkadot:›", + "polkadot:😁", + // Not the expected length + "polkadot:a", + "polkadot:aa", + "polkadot:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + ]; + for chain in invalid_chains { + assert!( + ChainId::try_from(chain.as_bytes()).is_err(), + "Chain ID {:?} should fail to parse for polkadot chains", + chain + ); + } + } + + #[test] + fn test_solana_chains() { + let valid_chains = [ + "solana:a", + "solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ", + "solana:8E9rvCKLFQia2Y35HXjjpWzj8weVo44K", + ]; + for chain in valid_chains { + assert!( + ChainId::try_from(chain.as_bytes()).is_ok(), + "Chain ID {:?} should not fail to parse for solana chains", + chain + ); + } + + let invalid_chains = [ + // Too short + "s", + "so", + "sol", + "sola", + "solan", + "solana", + "solana:", + // Not a Base58 string + "solana::", + "solana:›", + "solana:😁", + "solana:random-string", + // Valid base58 text, too long (34 chars) + "solana:TJ24pxm996UCBScuQRwjYo4wvPjUa8pzKo", + ]; + for chain in invalid_chains { + assert!( + ChainId::try_from(chain.as_bytes()).is_err(), + "Chain ID {:?} should fail to parse for generic chains", + chain + ); + } + } + + #[test] + fn test_generic_chains() { + let valid_chains = [ + // Edge cases + "abc:-", + "-as01-aa:A", + "12345678:abcdefghjklmnopqrstuvwxyzABCD012", + // Filecoin examples -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-23.md + "fil:t", + "fil:f", + // Tezos examples -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-26.md + "tezos:NetXdQprcVkpaWU", + "tezos:NetXm8tYqnMWky1", + // Cosmos examples -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-5.md + "cosmos:cosmoshub-2", + "cosmos:cosmoshub-3", + "cosmos:Binance-Chain-Tigris", + "cosmos:iov-mainnet", + "cosmos:x", + "cosmos:hash-", + "cosmos:hashed", + // Lisk examples -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-6.md + "lip9:9ee11e9df416b18b", + "lip9:e48feb88db5b5cf5", + // EOSIO examples -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-7.md + "eosio:aca376f206b8fc25a6ed44dbdc66547c", + "eosio:e70aaab8997e1dfce58fbfac80cbbb8f", + "eosio:4667b205c6838ef70ff7988f6e8257e8", + "eosio:1eaa0824707c8c16bd25145493bf062a", + // Stellar examples -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-28.md + "stellar:testnet", + "stellar:pubnet", + ]; + for chain in valid_chains { + println!("Testing right chain {:?}", chain); + assert!( + ChainId::try_from(chain.as_bytes()).is_ok(), + "Chain ID {:?} should not fail to parse for generic chains", + chain + ); + } + + let invalid_chains = [ + // Too short + "a", + "ab", + "01:", + "ab-:", + // Too long + "123456789:1", + "12345678:123456789123456789123456789123456", + "123456789:123456789123456789123456789123456", + // Unallowed characters + "::", + "c?1:›", + "de:😁", + ]; + for chain in invalid_chains { + println!("Testing wrong chain {:?}", chain); + assert!( + ChainId::try_from(chain.as_bytes()).is_err(), + "Chain ID {:?} should fail to parse for solana chains", + chain + ); + } + } + + #[test] + fn test_utility_functions() { + // These functions should never crash. We just check that here. + ChainId::ethereum_mainnet(); + ChainId::moonbeam_eth(); + ChainId::bitcoin_mainnet(); + ChainId::polkadot(); + ChainId::kusama(); + ChainId::kilt_spiritnet(); + ChainId::solana_mainnet(); + } +} From b9ae971ce0c2e3fff0b6962e2b56f1e659f913cb Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Fri, 17 Jun 2022 10:17:23 +0200 Subject: [PATCH 021/138] fix: chain ids now working fine --- utilities/assets/src/chain_id.rs | 83 +++++++++++++------------------- 1 file changed, 33 insertions(+), 50 deletions(-) diff --git a/utilities/assets/src/chain_id.rs b/utilities/assets/src/chain_id.rs index 73985b336c..d833a2ec18 100644 --- a/utilities/assets/src/chain_id.rs +++ b/utilities/assets/src/chain_id.rs @@ -19,18 +19,20 @@ use base58::FromBase58; use core::str; -use frame_support::{sp_runtime::traits::CheckedConversion, traits::ConstU32, BoundedVec}; +use frame_support::{traits::ConstU32, BoundedVec}; -const MINIMUM_NAMESPACE_LENGTH: u32 = 3; -const MAXIMUM_NAMESPACE_LENGTH: u32 = 8; -const MINIMUM_REFERENCE_LENGTH: u32 = 1; -const MAXIMUM_REFERENCE_LENGTH: u32 = 32; +const MINIMUM_NAMESPACE_LENGTH: usize = 3; +const MAXIMUM_NAMESPACE_LENGTH: usize = 8; +const MAXIMUM_NAMESPACE_LENGTH_U32: u32 = MAXIMUM_NAMESPACE_LENGTH as u32; +const MINIMUM_REFERENCE_LENGTH: usize = 1; +const MAXIMUM_REFERENCE_LENGTH: usize = 32; +const MAXIMUM_REFERENCE_LENGTH_U32: u32 = MAXIMUM_REFERENCE_LENGTH as u32; pub enum ChainId { Eip155(Eip155Reference), - Bip122(GenesisHexHashReference), - Dotsama(GenesisHexHashReference), - Solana(GenesisBase58HashReference), + Bip122(GenesisHexHashReference), + Dotsama(GenesisHexHashReference), + Solana(GenesisBase58HashReference), Generic(GenericChainId), } @@ -125,7 +127,7 @@ impl ChainId { } } -pub struct Eip155Reference(BoundedVec>); +pub struct Eip155Reference(BoundedVec>); impl Eip155Reference { #[allow(dead_code)] @@ -138,10 +140,7 @@ impl TryFrom<&[u8]> for Eip155Reference { type Error = ChainIdError; fn try_from(value: &[u8]) -> Result { - let input_length = value - .len() - .checked_into::() - .ok_or(ChainIdError::Reference(ReferenceError::TooLong))?; + let input_length = value.len(); if input_length < MINIMUM_REFERENCE_LENGTH { Err(ChainIdError::Reference(ReferenceError::TooShort)) } else if input_length > MAXIMUM_REFERENCE_LENGTH { @@ -160,24 +159,21 @@ impl TryFrom<&[u8]> for Eip155Reference { } } -pub struct GenesisHexHashReference(BoundedVec>); +// TODO: Add support for compilation-time checks on the value of L when supported. +pub struct GenesisHexHashReference([u8; L]); -impl GenesisHexHashReference { +impl GenesisHexHashReference { #[allow(dead_code)] pub(crate) fn from_slice_unchecked(slice: &[u8]) -> Self { - Self(slice.to_vec().try_into().unwrap()) + Self(slice.try_into().unwrap()) } } -// FIXME: Ensure that a size is given for the expected hash length (less than the max allowed size). -impl TryFrom<&[u8]> for GenesisHexHashReference { +impl TryFrom<&[u8]> for GenesisHexHashReference { type Error = ChainIdError; fn try_from(value: &[u8]) -> Result { - let input_length = value - .len() - .checked_into::() - .ok_or(ChainIdError::Reference(ReferenceError::TooLong))?; + let input_length = value.len(); if input_length < MINIMUM_REFERENCE_LENGTH { Err(ChainIdError::Reference(ReferenceError::TooShort)) } else if input_length > MAXIMUM_REFERENCE_LENGTH { @@ -193,30 +189,26 @@ impl TryFrom<&[u8]> for GenesisHexHashReference { Ok(()) } })?; - // Unchecked since we already checked for length - Ok(Self::from_slice_unchecked(value)) + value.try_into().map(Self).map_err(|_| ChainIdError::InvalidFormat) } } } // FIXME: Ensure that a size is given for the expected hash length (less than the max allowed size). -pub struct GenesisBase58HashReference(BoundedVec>); +pub struct GenesisBase58HashReference([u8; L]); -impl GenesisBase58HashReference { +impl GenesisBase58HashReference { #[allow(dead_code)] pub(crate) fn from_slice_unchecked(slice: &[u8]) -> Self { Self(slice.to_vec().try_into().unwrap()) } } -impl TryFrom<&[u8]> for GenesisBase58HashReference { +impl TryFrom<&[u8]> for GenesisBase58HashReference { type Error = ChainIdError; fn try_from(value: &[u8]) -> Result { - let input_length = value - .len() - .checked_into::() - .ok_or(ChainIdError::Reference(ReferenceError::TooLong))?; + let input_length = value.len(); if input_length < MINIMUM_REFERENCE_LENGTH { Err(ChainIdError::Reference(ReferenceError::TooShort)) } else if input_length > MAXIMUM_REFERENCE_LENGTH { @@ -228,8 +220,8 @@ impl TryFrom<&[u8]> for GenesisBase58HashReference { decoded_string .from_base58() .map_err(|_| ChainIdError::Reference(ReferenceError::InvalidCharacter))?; - // Unchecked since we already checked for length - Ok(Self::from_slice_unchecked(value)) + + value.try_into().map(Self).map_err(|_| ChainIdError::InvalidFormat) } } } @@ -253,8 +245,8 @@ impl TryFrom<&[u8]> for GenericChainId { type Error = ChainIdError; fn try_from(value: &[u8]) -> Result { - let input_length = value.len().checked_into::().ok_or(ChainIdError::InvalidFormat)?; - if input_length > MINIMUM_NAMESPACE_LENGTH + MAXIMUM_NAMESPACE_LENGTH + 1 { + let input_length = value.len(); + if input_length > MAXIMUM_NAMESPACE_LENGTH + MAXIMUM_REFERENCE_LENGTH + 1 { return Err(ChainIdError::InvalidFormat); } @@ -273,7 +265,7 @@ impl TryFrom<&[u8]> for GenericChainId { } } -pub struct ChainNamespace(BoundedVec>); +pub struct ChainNamespace(BoundedVec>); impl ChainNamespace { fn from_slice_unchecked(value: &[u8]) -> Self { @@ -285,10 +277,7 @@ impl TryFrom<&[u8]> for ChainNamespace { type Error = ChainIdError; fn try_from(value: &[u8]) -> Result { - let input_length = value - .len() - .checked_into::() - .ok_or(ChainIdError::Namespace(NamespaceError::TooLong))?; + let input_length = value.len(); if input_length < MINIMUM_NAMESPACE_LENGTH { Err(ChainIdError::Namespace(NamespaceError::TooShort)) } else if input_length > MAXIMUM_NAMESPACE_LENGTH { @@ -307,7 +296,7 @@ impl TryFrom<&[u8]> for ChainNamespace { } } -pub struct ChainReference(BoundedVec>); +pub struct ChainReference(BoundedVec>); impl ChainReference { fn from_slice_unchecked(value: &[u8]) -> Self { @@ -319,10 +308,7 @@ impl TryFrom<&[u8]> for ChainReference { type Error = ChainIdError; fn try_from(value: &[u8]) -> Result { - let input_length = value - .len() - .checked_into::() - .ok_or(ChainIdError::Reference(ReferenceError::TooLong))?; + let input_length = value.len(); if input_length < MINIMUM_REFERENCE_LENGTH { Err(ChainIdError::Reference(ReferenceError::TooShort)) } else if input_length > MAXIMUM_REFERENCE_LENGTH { @@ -398,7 +384,7 @@ mod test { for chain in valid_chains { assert!( ChainId::try_from(chain.as_bytes()).is_ok(), - "Chain ID {:?} should not fail to parse for polkadot chains", + "Chain ID {:?} should not fail to parse for bip122 chains", chain ); } @@ -425,7 +411,7 @@ mod test { for chain in invalid_chains { assert!( ChainId::try_from(chain.as_bytes()).is_err(), - "Chain ID {:?} should fail to parse for polkadot chains", + "Chain ID {:?} should fail to parse for bip122 chains", chain ); } @@ -480,7 +466,6 @@ mod test { #[test] fn test_solana_chains() { let valid_chains = [ - "solana:a", "solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ", "solana:8E9rvCKLFQia2Y35HXjjpWzj8weVo44K", ]; @@ -552,7 +537,6 @@ mod test { "stellar:pubnet", ]; for chain in valid_chains { - println!("Testing right chain {:?}", chain); assert!( ChainId::try_from(chain.as_bytes()).is_ok(), "Chain ID {:?} should not fail to parse for generic chains", @@ -576,7 +560,6 @@ mod test { "de:😁", ]; for chain in invalid_chains { - println!("Testing wrong chain {:?}", chain); assert!( ChainId::try_from(chain.as_bytes()).is_err(), "Chain ID {:?} should fail to parse for solana chains", From a43f2af02fbe8b6728530a100386aa785b4cb943 Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Fri, 17 Jun 2022 14:10:59 +0200 Subject: [PATCH 022/138] feat: asset id complete (untested) --- Cargo.lock | 2 +- utilities/assets/Cargo.toml | 5 +- utilities/assets/src/chain_id.rs | 582 ------------------------------- utilities/assets/src/lib.rs | 6 +- 4 files changed, 9 insertions(+), 586 deletions(-) delete mode 100644 utilities/assets/src/chain_id.rs diff --git a/Cargo.lock b/Cargo.lock index 01db749705..48f8aaa541 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3532,7 +3532,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" [[package]] -name = "kilt-caip-assets" +name = "kilt-asset-did" version = "1.6.2" dependencies = [ "base58", diff --git a/utilities/assets/Cargo.toml b/utilities/assets/Cargo.toml index dcbdc33ea4..e7f417bd14 100644 --- a/utilities/assets/Cargo.toml +++ b/utilities/assets/Cargo.toml @@ -2,14 +2,15 @@ authors = ["KILT "] description = "Asset DIDs and related structs, suitable for no_std environments." edition = "2021" -name = "kilt-caip-assets" +name = "kilt-asset-did" repository = "https://github.com/KILTprotocol/mashnet-node" version = "1.6.2" [dependencies] -frame-support = {branch = "polkadot-v0.9.23", default-features = false, git = "https://github.com/paritytech/substrate"} base58 = {version = "0.2.0", default-features = false} +frame-support = {branch = "polkadot-v0.9.23", default-features = false, git = "https://github.com/paritytech/substrate"} + [features] default = ["std"] diff --git a/utilities/assets/src/chain_id.rs b/utilities/assets/src/chain_id.rs deleted file mode 100644 index d833a2ec18..0000000000 --- a/utilities/assets/src/chain_id.rs +++ /dev/null @@ -1,582 +0,0 @@ -// KILT Blockchain – https://botlabs.org -// Copyright (C) 2019-2022 BOTLabs GmbH - -// The KILT Blockchain is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// The KILT Blockchain is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -// If you feel like getting in touch with us, you can do so at info@botlabs.org - -use base58::FromBase58; -use core::str; - -use frame_support::{traits::ConstU32, BoundedVec}; - -const MINIMUM_NAMESPACE_LENGTH: usize = 3; -const MAXIMUM_NAMESPACE_LENGTH: usize = 8; -const MAXIMUM_NAMESPACE_LENGTH_U32: u32 = MAXIMUM_NAMESPACE_LENGTH as u32; -const MINIMUM_REFERENCE_LENGTH: usize = 1; -const MAXIMUM_REFERENCE_LENGTH: usize = 32; -const MAXIMUM_REFERENCE_LENGTH_U32: u32 = MAXIMUM_REFERENCE_LENGTH as u32; - -pub enum ChainId { - Eip155(Eip155Reference), - Bip122(GenesisHexHashReference), - Dotsama(GenesisHexHashReference), - Solana(GenesisBase58HashReference), - Generic(GenericChainId), -} - -pub enum ChainIdError { - Namespace(NamespaceError), - Reference(ReferenceError), - InvalidFormat, -} - -pub enum NamespaceError { - TooLong, - TooShort, - InvalidCharacter, -} - -pub enum ReferenceError { - TooLong, - TooShort, - InvalidCharacter, -} - -impl TryFrom<&[u8]> for ChainId { - type Error = ChainIdError; - - fn try_from(value: &[u8]) -> Result { - match value { - // "eip155:" chains -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-3.md - [b'e', b'i', b'p', b'1', b'5', b'5', b':', chain_id @ ..] => { - Eip155Reference::try_from(chain_id).map(Self::Eip155) - } - // "bip122:" chains -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-4.md - [b'b', b'i', b'p', b'1', b'2', b'2', b':', chain_id @ ..] => { - GenesisHexHashReference::try_from(chain_id).map(Self::Bip122) - } - // "polkadot:" chains -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-13.md - [b'p', b'o', b'l', b'k', b'a', b'd', b'o', b't', b':', chain_id @ ..] => { - GenesisHexHashReference::try_from(chain_id).map(Self::Dotsama) - } - // "solana:" chains -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-30.md - [b's', b'o', b'l', b'a', b'n', b'a', b':', chain_id @ ..] => { - GenesisBase58HashReference::try_from(chain_id).map(Self::Solana) - } - // Other chains that are still compatible with the CAIP-2 spec -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md - chain_id => GenericChainId::try_from(chain_id).map(Self::Generic), - } - } -} - -impl ChainId { - pub fn ethereum_mainnet() -> Self { - Self::Eip155(Eip155Reference::from_slice_unchecked(b"1")) - } - - pub fn moonriver_eth() -> Self { - // Info taken from https://chainlist.org/ - Self::Eip155(Eip155Reference::from_slice_unchecked(b"1285")) - } - - pub fn moonbeam_eth() -> Self { - // Info taken from https://chainlist.org/ - Self::Eip155(Eip155Reference::from_slice_unchecked(b"1284")) - } - - pub fn bitcoin_mainnet() -> Self { - Self::Bip122(GenesisHexHashReference::from_slice_unchecked( - b"000000000019d6689c085ae165831e93", - )) - } - - pub fn polkadot() -> Self { - Self::Dotsama(GenesisHexHashReference::from_slice_unchecked( - b"91b171bb158e2d3848fa23a9f1c25182", - )) - } - - pub fn kusama() -> Self { - Self::Dotsama(GenesisHexHashReference::from_slice_unchecked( - b"b0a8d493285c2df73290dfb7e61f870f", - )) - } - - pub fn kilt_spiritnet() -> Self { - Self::Dotsama(GenesisHexHashReference::from_slice_unchecked( - b"411f057b9107718c9624d6aa4a3f23c1", - )) - } - - pub fn solana_mainnet() -> Self { - Self::Solana(GenesisBase58HashReference::from_slice_unchecked( - b"4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ", - )) - } -} - -pub struct Eip155Reference(BoundedVec>); - -impl Eip155Reference { - #[allow(dead_code)] - pub(crate) fn from_slice_unchecked(slice: &[u8]) -> Self { - Self(slice.to_vec().try_into().unwrap()) - } -} - -impl TryFrom<&[u8]> for Eip155Reference { - type Error = ChainIdError; - - fn try_from(value: &[u8]) -> Result { - let input_length = value.len(); - if input_length < MINIMUM_REFERENCE_LENGTH { - Err(ChainIdError::Reference(ReferenceError::TooShort)) - } else if input_length > MAXIMUM_REFERENCE_LENGTH { - Err(ChainIdError::Reference(ReferenceError::TooLong)) - } else { - value.iter().try_for_each(|c| { - if !(b'0'..=b'9').contains(c) { - Err(ChainIdError::Reference(ReferenceError::InvalidCharacter)) - } else { - Ok(()) - } - })?; - // Unchecked since we already checked for length - Ok(Self::from_slice_unchecked(value)) - } - } -} - -// TODO: Add support for compilation-time checks on the value of L when supported. -pub struct GenesisHexHashReference([u8; L]); - -impl GenesisHexHashReference { - #[allow(dead_code)] - pub(crate) fn from_slice_unchecked(slice: &[u8]) -> Self { - Self(slice.try_into().unwrap()) - } -} - -impl TryFrom<&[u8]> for GenesisHexHashReference { - type Error = ChainIdError; - - fn try_from(value: &[u8]) -> Result { - let input_length = value.len(); - if input_length < MINIMUM_REFERENCE_LENGTH { - Err(ChainIdError::Reference(ReferenceError::TooShort)) - } else if input_length > MAXIMUM_REFERENCE_LENGTH { - Err(ChainIdError::Reference(ReferenceError::TooLong)) - } else if input_length % 2 != 0 { - // Hex encoding can only have 2x characters - Err(ChainIdError::InvalidFormat) - } else { - value.iter().try_for_each(|c| { - if !matches!(c, b'0'..=b'9' | b'a'..=b'f') { - Err(ChainIdError::Reference(ReferenceError::InvalidCharacter)) - } else { - Ok(()) - } - })?; - value.try_into().map(Self).map_err(|_| ChainIdError::InvalidFormat) - } - } -} - -// FIXME: Ensure that a size is given for the expected hash length (less than the max allowed size). -pub struct GenesisBase58HashReference([u8; L]); - -impl GenesisBase58HashReference { - #[allow(dead_code)] - pub(crate) fn from_slice_unchecked(slice: &[u8]) -> Self { - Self(slice.to_vec().try_into().unwrap()) - } -} - -impl TryFrom<&[u8]> for GenesisBase58HashReference { - type Error = ChainIdError; - - fn try_from(value: &[u8]) -> Result { - let input_length = value.len(); - if input_length < MINIMUM_REFERENCE_LENGTH { - Err(ChainIdError::Reference(ReferenceError::TooShort)) - } else if input_length > MAXIMUM_REFERENCE_LENGTH { - Err(ChainIdError::Reference(ReferenceError::TooLong)) - } else { - let decoded_string = - str::from_utf8(value).map_err(|_| ChainIdError::Reference(ReferenceError::InvalidCharacter))?; - // Check for proper base58 encoding - decoded_string - .from_base58() - .map_err(|_| ChainIdError::Reference(ReferenceError::InvalidCharacter))?; - - value.try_into().map(Self).map_err(|_| ChainIdError::InvalidFormat) - } - } -} - -pub struct GenericChainId { - pub namespace: ChainNamespace, - pub reference: ChainReference, -} - -impl GenericChainId { - #[allow(dead_code)] - fn from_raw_unchecked(namespace: &[u8], reference: &[u8]) -> Self { - Self { - namespace: ChainNamespace::from_slice_unchecked(namespace), - reference: ChainReference::from_slice_unchecked(reference), - } - } -} - -impl TryFrom<&[u8]> for GenericChainId { - type Error = ChainIdError; - - fn try_from(value: &[u8]) -> Result { - let input_length = value.len(); - if input_length > MAXIMUM_NAMESPACE_LENGTH + MAXIMUM_REFERENCE_LENGTH + 1 { - return Err(ChainIdError::InvalidFormat); - } - - let mut components = value.split(|c| *c == b':'); - - let namespace = components - .next() - .ok_or(ChainIdError::InvalidFormat) - .and_then(ChainNamespace::try_from)?; - let reference = components - .next() - .ok_or(ChainIdError::InvalidFormat) - .and_then(ChainReference::try_from)?; - - Ok(Self { namespace, reference }) - } -} - -pub struct ChainNamespace(BoundedVec>); - -impl ChainNamespace { - fn from_slice_unchecked(value: &[u8]) -> Self { - Self(value.to_vec().try_into().unwrap()) - } -} - -impl TryFrom<&[u8]> for ChainNamespace { - type Error = ChainIdError; - - fn try_from(value: &[u8]) -> Result { - let input_length = value.len(); - if input_length < MINIMUM_NAMESPACE_LENGTH { - Err(ChainIdError::Namespace(NamespaceError::TooShort)) - } else if input_length > MAXIMUM_NAMESPACE_LENGTH { - Err(ChainIdError::Namespace(NamespaceError::TooLong)) - } else { - value.iter().try_for_each(|c| { - if !matches!(c, b'-' | b'a'..=b'z' | b'0'..=b'9') { - Err(ChainIdError::Namespace(NamespaceError::InvalidCharacter)) - } else { - Ok(()) - } - })?; - // Unchecked since we already checked for length - Ok(Self::from_slice_unchecked(value)) - } - } -} - -pub struct ChainReference(BoundedVec>); - -impl ChainReference { - fn from_slice_unchecked(value: &[u8]) -> Self { - Self(value.to_vec().try_into().unwrap()) - } -} - -impl TryFrom<&[u8]> for ChainReference { - type Error = ChainIdError; - - fn try_from(value: &[u8]) -> Result { - let input_length = value.len(); - if input_length < MINIMUM_REFERENCE_LENGTH { - Err(ChainIdError::Reference(ReferenceError::TooShort)) - } else if input_length > MAXIMUM_REFERENCE_LENGTH { - Err(ChainIdError::Reference(ReferenceError::TooLong)) - } else { - value.iter().try_for_each(|c| { - if !matches!(c, b'-' | b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9') { - Err(ChainIdError::Reference(ReferenceError::InvalidCharacter)) - } else { - Ok(()) - } - })?; - // Unchecked since we already checked for length - Ok(Self::from_slice_unchecked(value)) - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_eip155_chains() { - let valid_chains = [ - "eip155:1", - "eip155:5", - "eip155:99999999999999999999999999999999", - "eip155:0", - ]; - for chain in valid_chains { - assert!( - ChainId::try_from(chain.as_bytes()).is_ok(), - "Chain ID {:?} should not fail to parse for eip155 chains", - chain - ); - } - - let invalid_chains = [ - // Too short - "e", - "ei", - "eip", - "eip1", - "eip15", - "eip155", - "eip155:", - // Not a number - "eip155:a", - "eip155::", - "eip155:›", - "eip155:😁", - // Max chars + 1 - "eip155:999999999999999999999999999999999", - ]; - for chain in invalid_chains { - assert!( - ChainId::try_from(chain.as_bytes()).is_err(), - "Chain ID {:?} should fail to parse for eip155 chains", - chain - ); - } - } - - #[test] - fn test_bip122_chains() { - let valid_chains = [ - "bip122:000000000019d6689c085ae165831e93", - "bip122:12a765e31ffd4059bada1e25190f6e98", - "bip122:fdbe99b90c90bae7505796461471d89a", - "bip122:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - ]; - for chain in valid_chains { - assert!( - ChainId::try_from(chain.as_bytes()).is_ok(), - "Chain ID {:?} should not fail to parse for bip122 chains", - chain - ); - } - - let invalid_chains = [ - // Too short - "b", - "bi", - "bip", - "bip1", - "bip12", - "bip122", - "bip122:", - // Not an HEX string - "bip122:gg", - "bip122::", - "bip122:›", - "bip122:😁", - // Not the expected length - "bip122:a", - "bip122:aa", - "bip122:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - ]; - for chain in invalid_chains { - assert!( - ChainId::try_from(chain.as_bytes()).is_err(), - "Chain ID {:?} should fail to parse for bip122 chains", - chain - ); - } - } - - #[test] - fn test_dotsama_chains() { - let valid_chains = [ - "polkadot:b0a8d493285c2df73290dfb7e61f870f", - "polkadot:742a2ca70c2fda6cee4f8df98d64c4c6", - "polkadot:37e1f8125397a98630013a4dff89b54c", - "polkadot:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - ]; - for chain in valid_chains { - assert!( - ChainId::try_from(chain.as_bytes()).is_ok(), - "Chain ID {:?} should not fail to parse for polkadot chains", - chain - ); - } - - let invalid_chains = [ - // Too short - "p", - "po", - "pol", - "polk", - "polka", - "polkad", - "polkado", - "polkadot", - "polkadot:", - // Not an HEX string - "polkadot:gg", - "polkadot::", - "polkadot:›", - "polkadot:😁", - // Not the expected length - "polkadot:a", - "polkadot:aa", - "polkadot:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - ]; - for chain in invalid_chains { - assert!( - ChainId::try_from(chain.as_bytes()).is_err(), - "Chain ID {:?} should fail to parse for polkadot chains", - chain - ); - } - } - - #[test] - fn test_solana_chains() { - let valid_chains = [ - "solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ", - "solana:8E9rvCKLFQia2Y35HXjjpWzj8weVo44K", - ]; - for chain in valid_chains { - assert!( - ChainId::try_from(chain.as_bytes()).is_ok(), - "Chain ID {:?} should not fail to parse for solana chains", - chain - ); - } - - let invalid_chains = [ - // Too short - "s", - "so", - "sol", - "sola", - "solan", - "solana", - "solana:", - // Not a Base58 string - "solana::", - "solana:›", - "solana:😁", - "solana:random-string", - // Valid base58 text, too long (34 chars) - "solana:TJ24pxm996UCBScuQRwjYo4wvPjUa8pzKo", - ]; - for chain in invalid_chains { - assert!( - ChainId::try_from(chain.as_bytes()).is_err(), - "Chain ID {:?} should fail to parse for generic chains", - chain - ); - } - } - - #[test] - fn test_generic_chains() { - let valid_chains = [ - // Edge cases - "abc:-", - "-as01-aa:A", - "12345678:abcdefghjklmnopqrstuvwxyzABCD012", - // Filecoin examples -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-23.md - "fil:t", - "fil:f", - // Tezos examples -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-26.md - "tezos:NetXdQprcVkpaWU", - "tezos:NetXm8tYqnMWky1", - // Cosmos examples -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-5.md - "cosmos:cosmoshub-2", - "cosmos:cosmoshub-3", - "cosmos:Binance-Chain-Tigris", - "cosmos:iov-mainnet", - "cosmos:x", - "cosmos:hash-", - "cosmos:hashed", - // Lisk examples -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-6.md - "lip9:9ee11e9df416b18b", - "lip9:e48feb88db5b5cf5", - // EOSIO examples -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-7.md - "eosio:aca376f206b8fc25a6ed44dbdc66547c", - "eosio:e70aaab8997e1dfce58fbfac80cbbb8f", - "eosio:4667b205c6838ef70ff7988f6e8257e8", - "eosio:1eaa0824707c8c16bd25145493bf062a", - // Stellar examples -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-28.md - "stellar:testnet", - "stellar:pubnet", - ]; - for chain in valid_chains { - assert!( - ChainId::try_from(chain.as_bytes()).is_ok(), - "Chain ID {:?} should not fail to parse for generic chains", - chain - ); - } - - let invalid_chains = [ - // Too short - "a", - "ab", - "01:", - "ab-:", - // Too long - "123456789:1", - "12345678:123456789123456789123456789123456", - "123456789:123456789123456789123456789123456", - // Unallowed characters - "::", - "c?1:›", - "de:😁", - ]; - for chain in invalid_chains { - assert!( - ChainId::try_from(chain.as_bytes()).is_err(), - "Chain ID {:?} should fail to parse for solana chains", - chain - ); - } - } - - #[test] - fn test_utility_functions() { - // These functions should never crash. We just check that here. - ChainId::ethereum_mainnet(); - ChainId::moonbeam_eth(); - ChainId::bitcoin_mainnet(); - ChainId::polkadot(); - ChainId::kusama(); - ChainId::kilt_spiritnet(); - ChainId::solana_mainnet(); - } -} diff --git a/utilities/assets/src/lib.rs b/utilities/assets/src/lib.rs index a8234a7b81..31a94d9f9c 100644 --- a/utilities/assets/src/lib.rs +++ b/utilities/assets/src/lib.rs @@ -18,4 +18,8 @@ #![cfg_attr(not(feature = "std"), no_std)] -pub mod chain_id; +pub mod asset; +pub mod chain; + +pub use asset::*; +pub use chain::*; From df4f1aa5db87b982cb462bc2a525aeddd0ee0dfd Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Fri, 17 Jun 2022 14:59:28 +0200 Subject: [PATCH 023/138] most of unit tests for asset IDs in --- Cargo.lock | 1 + utilities/assets/Cargo.toml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 48f8aaa541..118e98d601 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3537,6 +3537,7 @@ version = "1.6.2" dependencies = [ "base58", "frame-support", + "sp-runtime", ] [[package]] diff --git a/utilities/assets/Cargo.toml b/utilities/assets/Cargo.toml index e7f417bd14..b5cef1e967 100644 --- a/utilities/assets/Cargo.toml +++ b/utilities/assets/Cargo.toml @@ -10,10 +10,12 @@ version = "1.6.2" base58 = {version = "0.2.0", default-features = false} frame-support = {branch = "polkadot-v0.9.23", default-features = false, git = "https://github.com/paritytech/substrate"} +sp-runtime = {branch = "polkadot-v0.9.23", default-features = false, git = "https://github.com/paritytech/substrate"} [features] default = ["std"] std = [ "frame-support/std", + "sp-runtime/std", ] From cd0748b56113555b03eb84bba1406da1a3417b2c Mon Sep 17 00:00:00 2001 From: Antonio Antonino Date: Fri, 17 Jun 2022 15:13:20 +0200 Subject: [PATCH 024/138] test: unit tests passing --- utilities/assets/src/asset.rs | 624 ++++++++++++++++++++++++++++++++++ utilities/assets/src/chain.rs | 589 ++++++++++++++++++++++++++++++++ 2 files changed, 1213 insertions(+) create mode 100644 utilities/assets/src/asset.rs create mode 100644 utilities/assets/src/chain.rs diff --git a/utilities/assets/src/asset.rs b/utilities/assets/src/asset.rs new file mode 100644 index 0000000000..8760f51e9b --- /dev/null +++ b/utilities/assets/src/asset.rs @@ -0,0 +1,624 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2022 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use frame_support::{traits::ConstU32, BoundedVec}; + +const MINIMUM_NAMESPACE_LENGTH: usize = 3; +const MAXIMUM_NAMESPACE_LENGTH: usize = 8; +const MAXIMUM_NAMESPACE_LENGTH_U32: u32 = MAXIMUM_NAMESPACE_LENGTH as u32; +const MINIMUM_REFERENCE_LENGTH: usize = 1; +const MAXIMUM_REFERENCE_LENGTH: usize = 64; +const MAXIMUM_REFERENCE_LENGTH_U32: u32 = MAXIMUM_REFERENCE_LENGTH as u32; +const MINIMUM_IDENTIFIER_LENGTH: usize = 1; +const MAXIMUM_IDENTIFIER_LENGTH: usize = 78; +const MAXIMUM_IDENTIFIER_LENGTH_U32: u32 = MAXIMUM_IDENTIFIER_LENGTH as u32; + +// 20 bytes -> 40 HEX characters +const EVM_SMART_CONTRACT_ADDRESS_LENGTH: usize = 40; + +pub enum AssetId { + Slip44(Slip44Reference), + Erc20(EvmSmartContractFungibleReference), + Erc721(EvmSmartContractNonFungibleReference), + Erc1155(EvmSmartContractNonFungibleReference), + Generic(GenericAssetId), +} + +#[derive(sp_runtime::RuntimeDebug)] +pub enum AssetIdError { + Namespace(NamespaceError), + Reference(ReferenceError), + Identifier(IdentifierError), + InvalidFormat, +} + +#[derive(sp_runtime::RuntimeDebug)] +pub enum NamespaceError { + TooLong, + TooShort, + InvalidCharacter, +} + +#[derive(sp_runtime::RuntimeDebug)] +pub enum ReferenceError { + TooLong, + TooShort, + InvalidCharacter, +} + +#[derive(sp_runtime::RuntimeDebug)] +pub enum IdentifierError { + TooLong, + TooShort, + InvalidCharacter, +} + +impl TryFrom<&[u8]> for AssetId { + type Error = AssetIdError; + + fn try_from(value: &[u8]) -> Result { + match value { + // "slip44:" tokens -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-20.md + [b's', b'l', b'i', b'p', b'4', b'4', b':', asset_reference @ ..] => { + Slip44Reference::try_from(asset_reference).map(Self::Slip44) + } + // "erc20:" tokens -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-21.md + [b'e', b'r', b'c', b'2', b'0', b':', asset_reference @ ..] => { + EvmSmartContractFungibleReference::try_from(asset_reference).map(Self::Erc20) + } + // "erc721:" tokens -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-22.md + [b'e', b'r', b'c', b'7', b'2', b'1', b':', asset_reference @ ..] => { + EvmSmartContractNonFungibleReference::try_from(asset_reference).map(Self::Erc721) + } + // "erc1155:" tokens -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-29.md + [b'e', b'r', b'c', b'1', b'1', b'5', b'5', b':', asset_reference @ ..] => { + EvmSmartContractNonFungibleReference::try_from(asset_reference).map(Self::Erc1155) + } + asset_id => GenericAssetId::try_from(asset_id).map(Self::Generic), + } + } +} + +pub struct Slip44Reference(BoundedVec>); + +impl Slip44Reference { + #[allow(dead_code)] + pub(crate) fn from_slice_unchecked(slice: &[u8]) -> Self { + Self(slice.to_vec().try_into().unwrap()) + } +} + +impl TryFrom<&[u8]> for Slip44Reference { + type Error = AssetIdError; + + fn try_from(value: &[u8]) -> Result { + let input_length = value.len(); + if input_length < MINIMUM_REFERENCE_LENGTH { + Err(AssetIdError::Reference(ReferenceError::TooShort)) + } else if input_length > MAXIMUM_REFERENCE_LENGTH { + Err(AssetIdError::Reference(ReferenceError::TooLong)) + } else { + value.iter().try_for_each(|c| { + if !(b'0'..=b'9').contains(c) { + Err(AssetIdError::Reference(ReferenceError::InvalidCharacter)) + } else { + Ok(()) + } + })?; + // Unchecked since we already checked for length + Ok(Self::from_slice_unchecked(value)) + } + } +} + +pub struct EvmSmartContractFungibleReference([u8; EVM_SMART_CONTRACT_ADDRESS_LENGTH]); + +impl EvmSmartContractFungibleReference { + #[allow(dead_code)] + pub(crate) fn from_slice_unchecked(slice: &[u8]) -> Self { + Self(slice.try_into().unwrap()) + } +} + +impl TryFrom<&[u8]> for EvmSmartContractFungibleReference { + type Error = AssetIdError; + + fn try_from(value: &[u8]) -> Result { + match value { + // If the prefix is "0x" => parse the address + [b'0', b'x', contract_address @ ..] => { + let inner: [u8; EVM_SMART_CONTRACT_ADDRESS_LENGTH] = + contract_address.try_into().map_err(|_| AssetIdError::InvalidFormat)?; + inner.iter().try_for_each(|c| { + if !matches!(c, b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F') { + Err(AssetIdError::Reference(ReferenceError::InvalidCharacter)) + } else { + Ok(()) + } + })?; + // Unchecked since we already checked for length + Ok(Self::from_slice_unchecked(contract_address)) + } + // Otherwise fail + _ => Err(AssetIdError::InvalidFormat), + } + } +} + +pub struct EvmSmartContractNonFungibleReference( + EvmSmartContractFungibleReference, + Option, +); + +impl EvmSmartContractNonFungibleReference { + #[allow(dead_code)] + pub(crate) fn from_raw_unchecked(reference: &[u8], id: Option<&[u8]>) -> Self { + Self(reference.try_into().unwrap(), id.map(|id| id.try_into().unwrap())) + } +} + +impl TryFrom<&[u8]> for EvmSmartContractNonFungibleReference { + type Error = AssetIdError; + + fn try_from(value: &[u8]) -> Result { + let mut components = value.split(|c| *c == b':'); + + let reference = components + .next() + .ok_or(AssetIdError::InvalidFormat) + .and_then(EvmSmartContractFungibleReference::try_from)?; + + let id = components + .next() + // Transform Option to Result