diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs index cba3df4751..207d2ea65a 100644 --- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -153,5 +153,6 @@ fn testnet_genesis( }, transaction_payment: Default::default(), treasury: Default::default(), + } } diff --git a/pallets/dao/Cargo.toml b/pallets/dao/Cargo.toml index eed508cab2..92f789585c 100644 --- a/pallets/dao/Cargo.toml +++ b/pallets/dao/Cargo.toml @@ -28,6 +28,7 @@ sp-core = { version = "6.0.0", default-features = false, git = "https://github.c # universal pallet-did = { path = "../did", package = "pallet-did", default-features = false } pallet-profile = { path = "../profile", version = "0.7.0", default-features = false } +pallet-reputation = { path = "../reputation", version = "0.7.0", default-features = false } # dev dependencies [dev-dependencies] diff --git a/pallets/dao/src/mock.rs b/pallets/dao/src/mock.rs index cdcc1c162a..84e920c72b 100644 --- a/pallets/dao/src/mock.rs +++ b/pallets/dao/src/mock.rs @@ -27,6 +27,7 @@ frame_support::construct_runtime!( Did: pallet_did::{Pallet, Call, Storage, Event}, Dao: pallet_dao::{Pallet, Call, Storage, Event}, Profile: pallet_profile::{Pallet, Call, Storage, Event}, + Reputation: pallet_reputation, } ); pub type Moment = u64; @@ -160,6 +161,21 @@ impl pallet_balances::Config for Test { type WeightInfo = (); } +parameter_types! { + #[derive(TypeInfo, MaxEncodedLen, Encode)] + pub MaximumRatingsPer: u32 = 5; + + pub DefaultReputation: i32 = 0; +} + +impl pallet_reputation::Config for Test { + type Event = Event; + type ReputationHandler = pallet_reputation::impls::ReputationHandler; + type DefaultReputation = DefaultReputation; + type MaximumRatingsPer = MaximumRatingsPer; +} + + // Build genesis storage according to the mock runtime. pub fn new_test_ext() -> sp_io::TestExternalities { let storage = system::GenesisConfig::default().build_storage::().unwrap(); diff --git a/pallets/profile/Cargo.toml b/pallets/profile/Cargo.toml index 58a2cb794f..6c7573e7d7 100644 --- a/pallets/profile/Cargo.toml +++ b/pallets/profile/Cargo.toml @@ -21,7 +21,8 @@ frame-support = { version = "4.0.0-dev", default-features = false, git = "https: frame-system = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.27" } sp-std = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.27" } log = { version = "0.4.14", default-features = false } -pallet-balances = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.27" } +pallet-balances = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.27" } +pallet-reputation = { version = "0.7.0", default-features = false, path = "../reputation"} [dev-dependencies] sp-core = { version = "6.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.27" } diff --git a/pallets/profile/src/benchmarking.rs b/pallets/profile/src/benchmarking.rs index 2aabe0dfe3..5058c3223f 100644 --- a/pallets/profile/src/benchmarking.rs +++ b/pallets/profile/src/benchmarking.rs @@ -50,7 +50,6 @@ fn create_profile_info(_num_fields: u32) -> Profile { name: username.try_into().unwrap(), interests: interests.try_into().unwrap(), balance: Some(balance), - reputation: u32::MAX, available_hours_per_week, additional_information: None, } diff --git a/pallets/profile/src/lib.rs b/pallets/profile/src/lib.rs index 74c3ad373c..8f76a42f44 100644 --- a/pallets/profile/src/lib.rs +++ b/pallets/profile/src/lib.rs @@ -77,6 +77,7 @@ mod tests; mod benchmarking; pub mod weights; + #[frame_support::pallet] pub mod pallet { use frame_support::{dispatch::DispatchResult, storage::bounded_vec::BoundedVec, pallet_prelude::*}; @@ -85,7 +86,11 @@ pub mod pallet { use frame_support::traits::Currency; use scale_info::TypeInfo; use crate::weights::WeightInfo; - + use pallet_reputation::{ + traits::ReputationHandler, + Pallet as ReputationPallet, + Rating + }; // Account, Balance are used in Profile Struct type AccountOf = ::AccountId; @@ -101,14 +106,13 @@ pub mod pallet { pub name: BoundedVec, pub interests: BoundedVec, pub balance: Option>, - pub reputation: u32, pub available_hours_per_week: u8, pub additional_information: Option>, } /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] - pub trait Config: frame_system::Config { + pub trait Config: frame_system::Config + pallet_reputation::Config { /// Because this pallet emits events, it depends on the runtime's definition of an event. type Event: From> + IsType<::Event>; @@ -262,7 +266,6 @@ pub mod pallet { name, interests, balance: Some(balance), - reputation: 0, available_hours_per_week, additional_information, }; @@ -276,6 +279,7 @@ pub mod pallet { // Initialize completed tasks list with default value. >::insert(owner, BoundedVec::default()); + ReputationPallet::::create_reputation_record(owner); // Increase profile count let new_count = Self::profile_count().checked_add(1).ok_or(>::ProfileCountOverflow)?; @@ -317,6 +321,8 @@ pub mod pallet { // Remove profile from storage >::remove(owner); + ReputationPallet::::remove_reputation_record(owner.clone()); + // Reduce profile count let new_count = Self::profile_count().saturating_sub(1); >::put(new_count); @@ -324,21 +330,7 @@ pub mod pallet { Ok(()) } - // Public function that adds reputation to a profile - pub fn add_reputation(owner: &T::AccountId) -> Result<(), DispatchError> { - - // Get current profile - let mut profile = Self::profiles(owner).ok_or(>::NoProfileCreated)?; - - // Increase reputation - profile.increase_reputation(); - - // Insert into storage a new profile - >::insert(owner, profile); - - Ok(()) - } - + // Public function that check if user has a profile pub fn has_profile(owner: &T::AccountId) -> Result { @@ -364,14 +356,6 @@ pub mod pallet { // Change the reputation on a Profile (TODO MVP2: Improve reputation functions) impl Profile { - pub fn increase_reputation(&mut self) { - self.reputation += 1; - } - - pub fn decrease_reputation(&mut self) { - self.reputation -= 1; - } - pub fn change_interests(&mut self, new_interests: BoundedVec) { self.interests = new_interests; } diff --git a/pallets/profile/src/mock.rs b/pallets/profile/src/mock.rs index 5be42d0ea0..989c4b2d41 100644 --- a/pallets/profile/src/mock.rs +++ b/pallets/profile/src/mock.rs @@ -22,9 +22,10 @@ frame_support::construct_runtime!( NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic, { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Profile: pallet_profile::{Pallet, Call, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + System: frame_system, + Profile: pallet_profile, + Balances: pallet_balances, + Reputation: pallet_reputation, } ); @@ -97,6 +98,21 @@ impl pallet_balances::Config for Test { type WeightInfo = (); } +parameter_types! { + #[derive(TypeInfo, MaxEncodedLen, Encode)] + pub MaximumRatingsPer: u32 = 5; + + pub DefaultReputation: i32 = 0; +} + +impl pallet_reputation::Config for Test { + type Event = Event; + type ReputationHandler = pallet_reputation::impls::ReputationHandler; + type DefaultReputation = DefaultReputation; + type MaximumRatingsPer = MaximumRatingsPer; +} + + // Build genesis storage according to the mock runtime. pub(crate) fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); diff --git a/pallets/profile/src/tests.rs b/pallets/profile/src/tests.rs index 58f0428a60..823bcc7636 100644 --- a/pallets/profile/src/tests.rs +++ b/pallets/profile/src/tests.rs @@ -2,6 +2,10 @@ use core::convert::TryInto; use frame_support::storage::bounded_vec::BoundedVec; use crate::{mock::*, Error}; use frame_support::{assert_noop, assert_ok}; +use pallet_reputation::{ + RepInfoOf, + Reputable, +}; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Constants and Functions used in TESTS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -51,12 +55,14 @@ fn verify_inputs_outputs_to_profile(){ // Get profile for current account let profile = Profile::profiles(10).expect("should found the profile"); + let maybe_reputation: Option> = RepInfoOf::::get(10); + assert!(maybe_reputation.is_some()); // Ensure that profile properties are assigned correctly assert_eq!(profile.name.into_inner(), &[1, 4]); - assert_eq!(profile.reputation, 0); assert_eq!(profile.interests.into_inner(), &[2,4]); assert_eq!(profile.available_hours_per_week, 40_u8); + assert_eq!(maybe_reputation.unwrap().reputation, 0i32); }); } diff --git a/pallets/reputation/Cargo.toml b/pallets/reputation/Cargo.toml new file mode 100644 index 0000000000..18358abaaa --- /dev/null +++ b/pallets/reputation/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "pallet-reputation" +version = "0.7.0" +description = "FRAME pallet for deriving the reputation of a given entity." +authors = ["UNIVERSALDOT FOUNDATION "] +homepage = "https://universaldot.foundation" +edition = "2021" +license = "Apache-2.0" +publish = false +repository = "https://github.com/UniversalDot/pallets" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.27" } +frame-support = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.27" } +frame-system = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.27" } + +[dev-dependencies] +sp-core = { version = "6.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.27" } +sp-io = { version = "6.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.27" } +sp-runtime = { version = "6.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.27" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", +] +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] +try-runtime = ["frame-support/try-runtime"] diff --git a/pallets/reputation/src/impls.rs b/pallets/reputation/src/impls.rs new file mode 100644 index 0000000000..aee801dacf --- /dev/null +++ b/pallets/reputation/src/impls.rs @@ -0,0 +1,63 @@ + +use crate::{ + pallet::{CredibilityUnit, ReputationUnit, Reputable, Rating}, + traits::{HasReputation, HasCredibility, HasAccountId} + +}; +use frame_support::{ + pallet_prelude::*, + inherent::Vec, + BoundedVec +}; + +pub struct ReputationHandler; + +impl crate::traits::ReputationHandler for ReputationHandler +where T: frame_system::Config + crate::Config +{ + + fn calculate_credibility(entity: &N, ratings: &BoundedVec) -> CredibilityUnit + where N: HasCredibility + { + CredibilityUnit::default() + } + + fn calculate_reputation(entity: &N, ratings: &BoundedVec) -> ReputationUnit + where N: HasCredibility + HasReputation + HasAccountId + { + let mut rep = entity.get_reputation(); + + let _: Vec<_> = ratings.iter().map(|r|{ + let diff: i32 = *r as i32 - 3i32; + rep += diff; + }).collect::<_>(); + + rep.try_into().expect("input vec is bounded, output is same length; qed") + } +} + + +impl HasCredibility for Reputable +where T: frame_system::Config +{ + fn get_credibility(&self) -> CredibilityUnit { + self.credibility + } + +} + +impl HasReputation for Reputable +where T: frame_system::Config +{ + fn get_reputation(&self) -> ReputationUnit { + self.reputation + } +} + +impl HasAccountId for Reputable +where T: frame_system::Config +{ + fn get_account_id(&self) -> &T::AccountId { + &self.account + } +} \ No newline at end of file diff --git a/pallets/reputation/src/lib.rs b/pallets/reputation/src/lib.rs new file mode 100644 index 0000000000..26bbbfd9fd --- /dev/null +++ b/pallets/reputation/src/lib.rs @@ -0,0 +1,176 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 UNIVERSALDOT FOUNDATION. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Reputation Pallet +//! +//! ## Version: 0.0.1 +//! +//! ## Overview +//! +//! Given an account id, create some way of recording the reputation of that entity. +//! Implementation assumes that ratings will be given by another credible accountid. +//! +//! implementation incomplete. + +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + + +pub mod traits; +pub mod impls; + + +#[frame_support::pallet] +pub mod pallet { + use frame_support::{ + pallet_prelude::*, + BoundedVec + }; + use frame_system::{ + pallet_prelude::*, + WeightInfo + }; + + pub type ReputationUnit = i32; + pub type CredibilityUnit = u32; + pub type Rating = u8; + use crate::traits::ReputationHandler; + + pub const MAX_CREDIBILITY: CredibilityUnit = 1000; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] + #[scale_info(skip_type_params(T))] + pub struct Reputable { + pub reputation: ReputationUnit, + pub credibility: CredibilityUnit, + // The aggregate of all ratings + pub aggregate_rating: u64, + pub num_of_ratings: u64, + pub account: T::AccountId, + } + + #[pallet::config] + pub trait Config: frame_system::Config { + type Event: From> + IsType<::Event>; + + /// The trait that defines how the pallet deals with reputation and credibility. + type ReputationHandler: ReputationHandler; + + /// The default reputation of an account. + type DefaultReputation: Get; + + /// Maximum number of ratings per action + type MaximumRatingsPer: Get + MaxEncodedLen + TypeInfo; + } + + #[pallet::storage] + #[pallet::getter(fn reputation_of)] + pub type RepInfoOf = StorageMap<_, Twox64Concat, T::AccountId, Reputable, OptionQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Reputation record created. + ReputationRecordCreated{who: T::AccountId}, + /// Reputation record removed. + ReputationRecordRemoved{who: T::AccountId}, + /// Account rated. + AccountRated{who: T::AccountId}, + } + + #[pallet::error] + pub enum Error { + /// You cannot create duplicate reputation records. + ReputationAlreadyExists, + /// Reputation record does not exist. + CannotRemoveNothing, + /// Reputation Record not found. + RecordNotFound + } + + impl Pallet { + + /// Creates a reputation record for a given account id. + pub fn create_reputation_record(account: &T::AccountId) -> DispatchResult { + + // Ensure that a record does not exist. + let rep_record = Self::reputation_of(&account); + ensure!(rep_record.is_none(), Error::::ReputationAlreadyExists); + + // Instantiate and insert into storage. + let rep = Reputable { + account: account.clone(), + reputation: T::DefaultReputation::get(), + credibility: MAX_CREDIBILITY / 2, + aggregate_rating: Default::default(), + num_of_ratings: Default::default(), + }; + + RepInfoOf::::insert(account, rep); + Self::deposit_event(Event::ReputationRecordCreated{who: account.clone()}); + Ok(()) + } + + /// Remove a reputation record from storage. + pub fn remove_reputation_record(account: T::AccountId) -> DispatchResult { + + // Ensure record exists. + let rep_record = Self::reputation_of(&account); + ensure!(rep_record.is_some(), Error::::CannotRemoveNothing); + + // Remove from storage. + RepInfoOf::::remove(&account); + Self::deposit_event(Event::ReputationRecordRemoved{who: account.clone()}); + + Ok(()) + } + + /// Rate the account and adjust the reputation and credibility as defined by the ReputationHandler. + pub fn rate_account(account: &T::AccountId, ratings: &BoundedVec) -> DispatchResult { + + // Get the old record. + let mut record: Reputable = RepInfoOf::::get(account).ok_or(Error::::RecordNotFound).unwrap(); + + // Calculate the new totals as defined in the ReputationHandle. + let new_credibility = T::ReputationHandler::calculate_credibility(&record, ratings); + let new_reputation = T::ReputationHandler::calculate_reputation(&record, ratings); + let ratings_sum = ratings.iter().map(|i| *i as u64).sum(); + + // Update the record and insert into storage. + record.reputation = new_reputation; + record.num_of_ratings = record.num_of_ratings.saturating_add(ratings.len() as u64); + record.aggregate_rating = record.aggregate_rating.saturating_add(ratings_sum); + record.credibility = new_credibility; + + let _ = RepInfoOf::::insert(&account, record); + + Self::deposit_event(Event::AccountRated{who: account.clone()}); + Ok(()) + } + } +} diff --git a/pallets/reputation/src/mock.rs b/pallets/reputation/src/mock.rs new file mode 100644 index 0000000000..533ddc1120 --- /dev/null +++ b/pallets/reputation/src/mock.rs @@ -0,0 +1,78 @@ +use crate as pallet_reputation; +use frame_support::{ + traits::{ConstU16, ConstU64}, + parameter_types, + pallet_prelude::*, +}; +use frame_system as system; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; +use crate::{ + impls::ReputationHandler, +}; + + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Reputation: pallet_reputation, + } +); + +impl system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = ConstU16<42>; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + #[derive(TypeInfo, MaxEncodedLen, Encode)] + pub MaximumRatingsPer: u32 = 5; + + pub DefaultReputation: i32 = 0; +} + +impl pallet_reputation::Config for Test { + type Event = Event; + type ReputationHandler = ReputationHandler; + type DefaultReputation = DefaultReputation; + type MaximumRatingsPer = MaximumRatingsPer; +} + + +// Build genesis storage according to the mock runtime. +pub fn new_test_ext() -> sp_io::TestExternalities { + system::GenesisConfig::default().build_storage::().unwrap().into() +} diff --git a/pallets/reputation/src/tests.rs b/pallets/reputation/src/tests.rs new file mode 100644 index 0000000000..32792c3431 --- /dev/null +++ b/pallets/reputation/src/tests.rs @@ -0,0 +1,63 @@ +use crate::{mock::*, Error, RepInfoOf}; +use frame_support::{assert_noop, assert_ok, bounded_vec}; + + +#[test] +fn test_reputation_can_be_created() { + new_test_ext().execute_with(|| { + + // Assert reputation can be created. + assert_ok!(Reputation::create_reputation_record(&0)); + assert!(RepInfoOf::::get(0).is_some()); + }); +} + +#[test] +fn test_reputation_can_be_removed() { + new_test_ext().execute_with(|| { + + // Assert reputation can be created. + assert_ok!(Reputation::create_reputation_record(&0)); + assert!(RepInfoOf::::get(0).is_some()); + + // Assert reputation can be removed. + assert_ok!(Reputation::remove_reputation_record(0u64)); + assert!(RepInfoOf::::get(0).is_none()); + }); +} + +#[test] +fn duplicate_records_cannot_be_created() { + new_test_ext().execute_with(|| { + + // Assert one record can be created. + assert_ok!(Reputation::create_reputation_record(&0)); + assert!(RepInfoOf::::get(0).is_some()); + + // Assert the same AccountId cannot. + assert_noop!(Reputation::create_reputation_record(&0), Error::::ReputationAlreadyExists); + }); +} + +#[test] +fn placeholder_rep_function_works() { + new_test_ext().execute_with(|| { + + // Setup default state. + assert_ok!(Reputation::create_reputation_record(&0)); + + // Assert logic follows as described in: https://github.com/UniversalDot/universal-dot-node/issues/37 + assert_ok!(Reputation::rate_account(&0, &bounded_vec![1u8, 1u8])); + let rep_record = RepInfoOf::::get(0).unwrap(); + assert!(rep_record.reputation == (-4)); + + assert_ok!(Reputation::rate_account(&0, &bounded_vec![5u8, 5u8])); + let rep_record = RepInfoOf::::get(0).unwrap(); + assert!(rep_record.reputation == 0); + + assert_ok!(Reputation::rate_account(&0, &bounded_vec![5u8, 5u8])); + let rep_record = RepInfoOf::::get(0).unwrap(); + assert!(rep_record.reputation == 4); + + }); +} \ No newline at end of file diff --git a/pallets/reputation/src/traits.rs b/pallets/reputation/src/traits.rs new file mode 100644 index 0000000000..27fa2252d9 --- /dev/null +++ b/pallets/reputation/src/traits.rs @@ -0,0 +1,54 @@ + // Copyright (C) 2022 UNIVERSALDOT FOUNDATION. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + ReputationUnit, + CredibilityUnit, + Rating, +}; +use frame_support::BoundedVec; + +/// Trait used to handle the reputation of a system. +/// Opinionated so that the user must submit a credibility rating. +/// This should be used to weigh the votes of a consumer's reputation against their credibility. +pub trait ReputationHandler +where T: frame_system::Config + crate::Config +{ + /// Calculate the new reputation of a voter based of a new score given. + fn calculate_reputation(item: &N, score: &BoundedVec) -> ReputationUnit + where N: HasCredibility + HasReputation + HasAccountId; + + /// Calculate the new credibility of the voter, it is used to determine how to weigh the votes. + /// Must return a value between 0 and 1000 higher is better + fn calculate_credibility(item: &N, score: &BoundedVec) -> CredibilityUnit; + + } + +pub trait HasReputation { + + /// Return the reputation for a given struct. + fn get_reputation(&self) -> ReputationUnit; +} + +pub trait HasCredibility { + + /// Return the credibility for a given struct. + fn get_credibility(&self) -> CredibilityUnit; +} + +pub trait HasAccountId { + fn get_account_id(&self) -> &T::AccountId; +} + diff --git a/pallets/task/Cargo.toml b/pallets/task/Cargo.toml index a0ca055416..240ab7c6e8 100644 --- a/pallets/task/Cargo.toml +++ b/pallets/task/Cargo.toml @@ -27,6 +27,7 @@ sp-std = { version = "4.0.0", default-features = false, git = "https://github.co # universal pallet-profile = { path = "../profile", version = "0.7.0", default-features = false } +pallet-reputation = { path = "../reputation", version = "0.7.0", default-features = false } pallet-dao = { path = "../dao", version = "0.7.0", default-features = false } sp-core = { version = "6.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.27" } diff --git a/pallets/task/src/benchmarking.rs b/pallets/task/src/benchmarking.rs index dd133e591c..6db516b5e9 100644 --- a/pallets/task/src/benchmarking.rs +++ b/pallets/task/src/benchmarking.rs @@ -247,6 +247,7 @@ benchmarks! { let budget = ::Currency::total_balance(&task_creator); let attachments = vec![0u8, s as u8]; let keywords = vec![0u8, s as u8]; + let ratings = vec![1,2,3,4,5]; // Create profile before creating a task create_profile::(); @@ -255,7 +256,7 @@ benchmarks! { let _ = PalletTask::::start_task(RawOrigin::Signed(volunteer.clone()).into(), hash_task); let _ = PalletTask::::complete_task(RawOrigin::Signed(volunteer).into(), hash_task); - }: accept_task(RawOrigin::Signed(task_creator.clone()), hash_task) + }: accept_task(RawOrigin::Signed(task_creator.clone()), hash_task, ratings.try_into().unwrap()) /* the code to be benchmarked */ verify { diff --git a/pallets/task/src/lib.rs b/pallets/task/src/lib.rs index 464abc5ab1..5451ebb565 100644 --- a/pallets/task/src/lib.rs +++ b/pallets/task/src/lib.rs @@ -82,6 +82,7 @@ //! - `accept_task` - Function used to accept completed task. //! Inputs: //! - task_id: T::Hash, +//! - ratings: BoundedVec //! After the task is accepted, its data is removed from storage. //! //! - `reject_task` - Function used to reject an already completed task. @@ -134,6 +135,8 @@ pub mod pallet { traits::Organization, traits }; + + use pallet_reputation::Rating; #[cfg(feature = "std")] use frame_support::serde::{Deserialize, Serialize}; @@ -183,7 +186,7 @@ pub mod pallet { /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] - pub trait Config: frame_system::Config + pallet_profile::Config { + pub trait Config: frame_system::Config + pallet_reputation::Config + pallet_profile::Config { /// Because this pallet emits events, it depends on the runtime's definition of an event. type Event: From> + IsType<::Event>; @@ -459,7 +462,7 @@ pub mod pallet { /// Function to accept a completed task. [origin, task_id] #[transactional] #[pallet::weight(::WeightInfo::accept_task(0,0))] - pub fn accept_task(origin: OriginFor, task_id: T::Hash) -> DispatchResult { + pub fn accept_task(origin: OriginFor, task_id: T::Hash, ratings: BoundedVec) -> DispatchResult { // Check that the extrinsic was signed and get the signer. let signer = ensure_signed(origin)?; @@ -475,7 +478,7 @@ pub mod pallet { ::Currency::transfer(&signer, &task.volunteer, task.budget, ExistenceRequirement::AllowDeath)?; // Accept task and update storage. - Self::accept_completed_task(&signer, &mut task, &task_id)?; + Self::accept_completed_task(&signer, &mut task, &task_id, &ratings)?; // Add task to completed tasks list of volunteer's profile. pallet_profile::Pallet::::add_task_to_completed_tasks(&task.volunteer, task_id)?; @@ -774,7 +777,7 @@ pub mod pallet { } // Internal helper function, checks Must be called before calling this function. - fn accept_completed_task(task_initiator: &T::AccountId, task: &mut Task, task_id: &T::Hash) -> Result<(), DispatchError> { + fn accept_completed_task(task_initiator: &T::AccountId, task: &mut Task, task_id: &T::Hash, ratings: &BoundedVec) -> Result<(), DispatchError> { // Remove from ownership >::try_mutate(&task_initiator, |owned| { @@ -787,10 +790,10 @@ pub mod pallet { // Update task state task.status = TaskStatus::Accepted; - >::insert(task_id, task); + >::insert(task_id, &*task); // Reward reputation points to profiles who created/completed a task - Self::handle_reputation(task_id)?; + Self::handle_reputation(&task.status, &task.volunteer, &ratings)?; // remove task once accepted >::remove(task_id); @@ -881,15 +884,11 @@ pub mod pallet { } // Handles reputation update for profiles - fn handle_reputation(task_id: &T::Hash) -> Result<(), DispatchError> { - - // Check if task exists - let task = Self::tasks(&task_id).ok_or(>::TaskNotExist)?; - + fn handle_reputation(task_status: &TaskStatus, account: &T::AccountId, ratings: &BoundedVec) -> Result<(), DispatchError> { // Ensure that reputation is added only when task is in status Accepted - if task.status == TaskStatus::Accepted { - pallet_profile::Pallet::::add_reputation(&task.initiator)?; - pallet_profile::Pallet::::add_reputation(&task.volunteer)?; + if *task_status == TaskStatus::Accepted { + // TODO:pallet_profile::Pallet::::add_reputation(&task.initiator)?; + pallet_reputation::Pallet::::rate_account(account, ratings); } Ok(()) diff --git a/pallets/task/src/mock.rs b/pallets/task/src/mock.rs index 58b80a0b11..161fb20553 100644 --- a/pallets/task/src/mock.rs +++ b/pallets/task/src/mock.rs @@ -33,6 +33,7 @@ frame_support::construct_runtime!( Profile: pallet_profile::{Pallet, Call, Storage, Event}, Time: pallet_timestamp::{Pallet, Call, Storage, Inherent}, Task: pallet_task::{Pallet, Call, Storage, Event}, + Reputation: pallet_reputation::{Pallet, Storage, Event}, } ); @@ -202,6 +203,20 @@ impl pallet_task::traits::Organization for Test { } } +parameter_types! { + #[derive(TypeInfo, MaxEncodedLen, Encode)] + pub MaximumRatingsPer: u32 = 5; + + pub DefaultReputation: i32 = 0; +} + +impl pallet_reputation::Config for Test { + type Event = Event; + type ReputationHandler = pallet_reputation::impls::ReputationHandler; + type DefaultReputation = DefaultReputation; + type MaximumRatingsPer = MaximumRatingsPer; +} + pub static ALICE : Lazy = Lazy::new(||{sr25519::Public::from_raw([1u8; 32])}); pub static BOB : Lazy = Lazy::new(||{sr25519::Public::from_raw([2u8; 32])}); pub static TED : Lazy = Lazy::new(||{sr25519::Public::from_raw([10u8; 32])}); diff --git a/pallets/task/src/tests.rs b/pallets/task/src/tests.rs index 6d4d5a2d7c..79c9f7c42e 100644 --- a/pallets/task/src/tests.rs +++ b/pallets/task/src/tests.rs @@ -5,6 +5,7 @@ use frame_support::traits::fungible::Inspect; use frame_support::storage::bounded_vec::BoundedVec; use frame_support::{assert_noop, assert_ok, traits::{UnixTime, Hooks}}; use sp_core::H256; +use pallet_reputation::RepInfoOf; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Constants and Functions used in TESTS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -61,6 +62,11 @@ fn additional_info() -> BoundedVec { vec![1u8, 4].try_into().unwrap() } +fn get_ratings() -> BoundedVec { + // +2 rating overall + vec![1, 5, 5].try_into().expect("test") +} + fn get_deadline(multiple: u64) -> u64 { // deadline is current time + 1 hour let deadline =