From 9a09e627ba72decc45d98be583389e48e2424a21 Mon Sep 17 00:00:00 2001 From: didiermis Date: Tue, 8 Nov 2022 12:27:29 -0600 Subject: [PATCH 1/8] Add unit tests for initial setup, sudo admin registration & users. Fix #152 --- pallets/fund-admin/src/functions.rs | 2 +- pallets/fund-admin/src/mock.rs | 6 +- pallets/fund-admin/src/tests.rs | 329 ++++++++++++++++++++++++++-- 3 files changed, 319 insertions(+), 18 deletions(-) diff --git a/pallets/fund-admin/src/functions.rs b/pallets/fund-admin/src/functions.rs index f99f86a7..fef4cb4a 100644 --- a/pallets/fund-admin/src/functions.rs +++ b/pallets/fund-admin/src/functions.rs @@ -1672,7 +1672,7 @@ impl Pallet { //Remove user from UsersInfo storage map >::remove(admin.clone()); - // Remove administrator to rbac pallet + // Remove administrator from rbac pallet T::Rbac::remove_role_from_user( admin.clone(), Self::pallet_id(), diff --git a/pallets/fund-admin/src/mock.rs b/pallets/fund-admin/src/mock.rs index b8fba3b8..af3578b4 100644 --- a/pallets/fund-admin/src/mock.rs +++ b/pallets/fund-admin/src/mock.rs @@ -1,4 +1,4 @@ -use crate as pallet_proxy_financial; +use crate as pallet_fund_admin; use frame_support::parameter_types; use frame_system as system; use sp_core::H256; @@ -19,7 +19,7 @@ frame_support::construct_runtime!( UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Config, Storage, Event}, - FundAdmin: pallet_proxy_financial::{Pallet, Call, Storage, Event}, + FundAdmin: pallet_fund_admin::{Pallet, Call, Storage, Event}, Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, RBAC: pallet_rbac::{Pallet, Call, Storage, Event}, } @@ -78,7 +78,7 @@ parameter_types! { } -impl pallet_proxy_financial::Config for Test { +impl pallet_fund_admin::Config for Test { type Event = Event; type RemoveOrigin = EnsureRoot; type ProjectNameMaxLen = ProjectNameMaxLen; diff --git a/pallets/fund-admin/src/tests.rs b/pallets/fund-admin/src/tests.rs index bf587ceb..3c8cc47c 100644 --- a/pallets/fund-admin/src/tests.rs +++ b/pallets/fund-admin/src/tests.rs @@ -1,7 +1,8 @@ use crate::{mock::*, Error, types::*, Config}; -use frame_support::{assert_ok, BoundedVec, traits::ConstU32, assert_noop}; +use frame_support::{assert_ok, BoundedVec, traits::ConstU32, assert_noop, error::BadOrigin, bounded_vec}; use pallet_rbac::types::RoleBasedAccessControl; use sp_io::hashing::blake2_256; +use sp_runtime::DispatchResult; use std::vec; type RbacErr = pallet_rbac::Error; @@ -20,25 +21,325 @@ fn return_field_name(name: &str) -> FieldName { let name: BoundedVec> = name.as_bytes().to_vec().try_into().unwrap_or_default(); name } + +fn return_field_description(description: &str) -> FieldDescription { + let description: BoundedVec> = description.as_bytes().to_vec().try_into().unwrap_or_default(); + description +} + +fn register_administrator() -> DispatchResult { + FundAdmin::sudo_add_administrator( + Origin::root(), + 1, + return_field_name("Administrator"), + ).map_err(|_| Error::::UserAlreadyRegistered + )?; + Ok(()) +} + +fn return_user(user_account:u64, user_name: Option<&str>, user_role: Option, action: CUDAction) -> BoundedVec<(u64, Option>, + Option, + CUDAction), MaxRegistrationsAtTime> { + let mut users: BoundedVec<(u64, Option>, + Option, + CUDAction), MaxRegistrationsAtTime> = bounded_vec![]; + let field_name: BoundedVec = BoundedVec::try_from(vec![return_field_name(user_name.unwrap_or_default())]).unwrap_or_default(); + users.try_push((user_account, Some(field_name), user_role, action)).unwrap_or_default(); + users +} + +fn field_name_to_string(boundedvec: &BoundedVec>) -> String { + let mut s = String::new(); + for b in boundedvec.iter() { + s.push(*b as char); + } + s +} + +fn field_description_to_string(boundedvec: &BoundedVec>) -> String { + let mut s = String::new(); + for b in boundedvec.iter() { + s.push(*b as char); + } + s +} + +// I N I T I A L +// ----------------------------------------------------------------------------------------- +#[test] +fn cannon_initialize_pallet_twice_shouldnt_work() { + new_test_ext().execute_with(|| { + assert_noop!( + FundAdmin::initial_setup( + Origin::root()), + RbacErr::ScopeAlreadyExists); + }); +} + +#[test] +fn sudo_register_administrator_account_works() { + new_test_ext().execute_with(|| { + let alice_name = return_field_name("Alice Keys"); + assert_ok!(FundAdmin::sudo_add_administrator( + Origin::root(), + 2, + alice_name.clone() + )); + assert!(FundAdmin::users_info(2).is_some()); + }); +} + +#[test] +fn sudo_a_non_sudo_user_cannot_register_administrator_account_shouldnt_work() { + new_test_ext().execute_with(|| { + let alice_name = return_field_name("Alice Keys"); + assert_noop!( + FundAdmin::sudo_add_administrator(Origin::signed(1), 2, alice_name.clone()), + BadOrigin + ); + }); +} + +#[test] +fn sudo_cannot_register_an_administrator_account_twice_shouldnt_work() { + new_test_ext().execute_with(|| { + let alice_name = return_field_name("Alice Keys"); + assert_ok!(FundAdmin::sudo_add_administrator( + Origin::root(), + 2, + alice_name.clone() + )); + assert_noop!( + FundAdmin::sudo_add_administrator(Origin::root(), 2, alice_name.clone()), + Error::::UserAlreadyRegistered + ); + }); +} + +#[test] +fn sudo_delete_administrator_account_works() { + new_test_ext().execute_with(|| { + let alice_name = return_field_name("Alice Keys"); + assert_ok!(FundAdmin::sudo_add_administrator( + Origin::root(), + 2, + alice_name.clone() + )); + assert!(FundAdmin::users_info(2).is_some()); + + assert_ok!(FundAdmin::sudo_remove_administrator( + Origin::root(), + 2, + )); + assert!(FundAdmin::users_info(2).is_none()); + }); +} + +#[test] +fn sudo_cannot_delete_an_administrator_account_that_doesnt_exist_shouldnt_work() { + new_test_ext().execute_with(|| { + assert_noop!( + FundAdmin::sudo_remove_administrator(Origin::root(), 2), + Error::::UserNotRegistered + ); + }); +} + + // U S E R S // ----------------------------------------------------------------------------------------- #[test] -fn register_admin_works() { +fn users_register_administrator_account_works() { + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); + + assert_ok!(FundAdmin::users( + Origin::signed(1), + return_user(2, Some("Alice Administrator"), Some(ProxyRole::Administrator), CUDAction::Create) + )); + + assert!(FundAdmin::users_info(2).is_some()); + }); +} + +#[test] +fn users_register_builder_account_works() { + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); + + assert_ok!(FundAdmin::users( + Origin::signed(1), + return_user(2, Some("Alice Builder"), Some(ProxyRole::Builder), CUDAction::Create) + )); + + assert!(FundAdmin::users_info(2).is_some()); + }); +} + +#[test] +fn users_register_investor_account_works() { + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); + + assert_ok!(FundAdmin::users( + Origin::signed(1), + return_user(2, Some("Alice Investor"), Some(ProxyRole::Investor), CUDAction::Create) + )); + + assert!(FundAdmin::users_info(2).is_some()); + }); +} + +#[test] +fn users_register_issuer_account_works() { new_test_ext().execute_with(|| { - let admin = 1; + assert_ok!(register_administrator()); + + assert_ok!(FundAdmin::users( + Origin::signed(1), + return_user(2, Some("Alice Issuer"), Some(ProxyRole::Issuer), CUDAction::Create) + )); + + assert!(FundAdmin::users_info(2).is_some()); }); } -// #[test] -// fn sudo_register_administrator_account_works() { -// new_test_ext().execute_with(|| { -// // let alice_name = return_field_name("Alice Keys"); -// // assert_ok!(FundAdmin::sudo_add_administrator( -// // Origin::signed(1), -// // 1, -// // alice_name.clone() -// // )); +#[test] +fn users_register_regional_center_account_works() { + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); + + assert_ok!(FundAdmin::users( + Origin::signed(1), + return_user(2, Some("Alice Regional Center"), Some(ProxyRole::RegionalCenter), CUDAction::Create) + )); + + assert!(FundAdmin::users_info(2).is_some()); + }); +} -// }); -// } +#[test] +fn users_a_non_registered_admin_tries_to_register_an_account_shouldnt_work() { + new_test_ext().execute_with(|| { + assert_noop!( + FundAdmin::users( + Origin::signed(1), + return_user(2, Some("Alice Regional Center"), Some(ProxyRole::RegionalCenter), CUDAction::Create) + ), + RbacErr::NotAuthorized + ); + }); +} + +#[test] +fn users_a_registered_admin_tries_to_register_an_account_twice_shouldnt_work() { + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); + + assert_ok!(FundAdmin::users( + Origin::signed(1), + return_user(2, Some("Alice Regional Center"), Some(ProxyRole::RegionalCenter), CUDAction::Create) + )); + + assert_noop!( + FundAdmin::users( + Origin::signed(1), + return_user(2, Some("Alice Regional Center"), Some(ProxyRole::RegionalCenter), CUDAction::Create) + ), + Error::::UserAlreadyRegistered + ); + }); +} + +#[test] +fn users_update_a_registered_account_works() { + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); + + assert_ok!(FundAdmin::users( + Origin::signed(1), + return_user(2, Some("Alice Regional Center"), Some(ProxyRole::RegionalCenter), CUDAction::Create) + )); + + assert_ok!(FundAdmin::users( + Origin::signed(1), + return_user(2, Some("Alice Regional Center Updated"), None, CUDAction::Update) + )); + + assert_eq!(field_name_to_string(&FundAdmin::users_info(2).unwrap().name), String::from("Alice Regional Center Updated")); + }); +} + +#[test] +fn users_update_role_of_a_registered_account_works() { + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); + + assert_ok!(FundAdmin::users( + Origin::signed(1), + return_user(2, Some("Alice Regional Center"), Some(ProxyRole::RegionalCenter), CUDAction::Create) + )); + + assert_ok!(FundAdmin::users( + Origin::signed(1), + return_user(2, Some("Alice Investor"), Some(ProxyRole::Investor), CUDAction::Update) + )); + + assert_eq!(FundAdmin::users_info(2).unwrap().role, ProxyRole::Investor); + assert_eq!(FundAdmin::users_info(2).unwrap().name, return_field_name("Alice Investor"));00 + }); +} + + +#[test] +fn users_update_a_non_registered_account_shouldnt_work() { + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); + + assert_noop!( + FundAdmin::users( + Origin::signed(1), + return_user(2, Some("Alice Regional Center"), Some(ProxyRole::RegionalCenter), CUDAction::Update) + ), + Error::::UserNotRegistered + ); + }); +} + +//TODO: cannot update a registered users if the user has assigned projects + +#[test] +fn users_delete_a_registered_account_works() { + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); + + assert_ok!(FundAdmin::users( + Origin::signed(1), + return_user(2, Some("Alice Regional Center"), Some(ProxyRole::RegionalCenter), CUDAction::Create) + )); + + assert_ok!(FundAdmin::users( + Origin::signed(1), + return_user(2, None, None, CUDAction::Delete) + )); + + assert!(FundAdmin::users_info(2).is_none()); + }); +} + +#[test] +fn users_delete_a_non_registered_account_shouldnt_work() { + new_test_ext().execute_with(|| { + assert_ok!(register_administrator()); + + assert_noop!( + FundAdmin::users( + Origin::signed(1), + return_user(2, None, None, CUDAction::Delete) + ), + Error::::UserNotRegistered + ); + }); +} +//TODO: cannot delete a registered users if the user has assigned projects From b44921c8b5c1126593c3af7f970aa74488ad6a75 Mon Sep 17 00:00:00 2001 From: didiermis Date: Tue, 8 Nov 2022 16:38:34 -0600 Subject: [PATCH 2/8] Update pallet events & pallet errors --- pallets/fund-admin/src/functions.rs | 6 ++-- pallets/fund-admin/src/lib.rs | 54 ++++++----------------------- 2 files changed, 13 insertions(+), 47 deletions(-) diff --git a/pallets/fund-admin/src/functions.rs b/pallets/fund-admin/src/functions.rs index fef4cb4a..20b20e0a 100644 --- a/pallets/fund-admin/src/functions.rs +++ b/pallets/fund-admin/src/functions.rs @@ -972,7 +972,7 @@ impl Pallet { match drawdown_data.drawdown_type { DrawdownType::EB5 => { // Ensure transactions_feedback is some - let mod_transactions_feedback = transactions_feedback.ok_or(Error::::EB5FeebackMissing)?; + let mod_transactions_feedback = transactions_feedback.ok_or(Error::::EB5MissingFeedback)?; for (transaction_id, field_description) in mod_transactions_feedback { // Update transaction feedback @@ -1259,7 +1259,7 @@ impl Pallet { Self::is_authorized(admin.clone(), &Self::get_global_scope(), ProxyPermission::Expenditures)?; // Ensure projects is not empty - ensure!(!projects.is_empty(), Error::::ProjectsIsEmpty); + ensure!(!projects.is_empty(), Error::::InflationRateMissingProjectIds); // Match each CUD action for project in projects { @@ -1313,7 +1313,7 @@ impl Pallet { /// Get global scope pub fn get_global_scope() -> [u8;32] { - let global_scope = >::try_get().map_err(|_| Error::::NoneValue).unwrap(); + let global_scope = >::try_get().map_err(|_| Error::::NoGlobalScopeValueWasFound).unwrap(); global_scope } diff --git a/pallets/fund-admin/src/lib.rs b/pallets/fund-admin/src/lib.rs index 43115bf1..142fab47 100644 --- a/pallets/fund-admin/src/lib.rs +++ b/pallets/fund-admin/src/lib.rs @@ -21,7 +21,6 @@ mod types; // - Remove unused pallet events // - Add internal documentation for each extrinsic // - Add external documentation for each extrinsic -// - Update hasher for each storage map depending on the use case // - Fix typos #[frame_support::pallet] @@ -231,9 +230,9 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// Project was created + /// Project was created successfully ProjectCreated(T::AccountId, [u8;32]), - /// Proxy setup completed + /// Proxy initial setup completed ProxySetupCompleted, /// User registered successfully UserAdded(T::AccountId), @@ -255,8 +254,6 @@ pub mod pallet { UserDeleted(T::AccountId), /// Expenditure was created successfully ExpenditureCreated, - /// A bugdet was created successfully - BudgetCreated([u8;32]), /// Expenditure was edited successfully ExpenditureEdited([u8;32]), /// Expenditure was deleted successfully @@ -279,7 +276,6 @@ pub mod pallet { DrawdownApproved([u8;32]), /// Drawdown was rejected successfully DrawdownRejected([u8;32]), - } // E R R O R S @@ -289,26 +285,22 @@ pub mod pallet { /// Error names should be descriptive. /// TODO: map each constant type used by bounded vecs to a pallet error /// when boundaries are exceeded - /// TODO: Update and remove unused pallet errors - NoneValue, + /// No value was found for the global scope + NoGlobalScopeValueWasFound, /// Project ID is already in use ProjectIdAlreadyInUse, - /// Timestamp error + /// Timestamp was not genereated correctly TimestampError, /// Completion date must be later than creation date CompletionDateMustBeLater, - /// User is already registered + /// User is already registered in the site UserAlreadyRegistered, - /// Project is not found + /// Project was not found ProjectNotFound, - ///Date can not be in the past - DateCanNotBeInThePast, /// Project is not active anymore ProjectIsAlreadyCompleted, /// Can not delete a completed project CannotDeleteCompletedProject, - /// Global scope is not set - GlobalScopeNotSet, /// User is not registered UserNotRegistered, /// User has been already added to the project @@ -317,8 +309,6 @@ pub mod pallet { MaxUsersPerProjectReached, /// Max number of projects per user reached MaxProjectsPerUserReached, - /// User already has the role - UserAlreadyHasRole, /// User is not assigned to the project UserNotAssignedToProject, /// Can not register administator role @@ -333,8 +323,6 @@ pub mod pallet { MaxRegionalCenterPerProjectReached, /// Can not remove administator role CannotRemoveAdminRole, - /// Can not delete an user with active projects - CannotDeleteUserWithAssignedProjects, /// Can not add admin role at user project assignment CannotAddAdminRole, /// User can not have more than one role at the same time @@ -345,18 +333,10 @@ pub mod pallet { ExpenditureAlreadyExists, /// Max number of expenditures per project reached MaxExpendituresPerProjectReached, - /// Name for expenditure is too long - NameTooLong, - /// There is no expenditure with such project id - NoExpendituresFound, /// Field name can not be empty EmptyExpenditureName, /// Expenditure does not belong to the project ExpenditureDoesNotBelongToProject, - /// There is no budgets for the project - ThereIsNoBudgetsForTheProject, - /// Budget id is not found - BudgetNotFound, /// Drowdown id is not found DrawdownNotFound, /// Invalid amount @@ -375,18 +355,12 @@ pub mod pallet { MaxDrawdownsPerProjectReached, /// Can not modify a completed drawdown CannotEditDrawdown, - /// Can not delete a completed drawdown - CannotDeleteCompletedDrawdown, /// Can not modify a transaction at this moment CannotEditTransaction, - /// Can not delete a completed transaction - CannotDeleteCompletedTransaction, /// Drawdown is already completed DrawdownIsAlreadyCompleted, /// Transaction is already completed TransactionIsAlreadyCompleted, - /// Expenditure type does not match project type - InvalidExpenditureType, /// User does not have the specified role UserDoesNotHaveRole, /// Transactions vector is empty @@ -397,8 +371,6 @@ pub mod pallet { DrawdownHasNoTransactions, /// Cannot submit transaction CannotSubmitTransaction, - /// Drawdown can not be submitted - CannotSubmitDrawdown, /// Drawdown can not be approved if is not in submitted status DrawdownIsNotInSubmittedStatus, /// Transactions is not in submitted status @@ -427,18 +399,12 @@ pub mod pallet { BulkUploadDescriptionRequired, /// Administator can not delete themselves AdministatorsCannotDeleteThemselves, - /// No transactions were provided for bulk upload - NoTransactionsProvidedForBulkUpload, /// No feedback was provided for bulk upload NoFeedbackProvidedForBulkUpload, - /// Feedback provided for bulk upload should be one - FeedbackProvidedForBulkUploadShouldBeOne, - /// Bulkupdate param is missed to execute a bulkupload drawdown - BulkUpdateIsRequired, /// NO feedback for EN5 drawdown was provided - EB5FeebackMissing, - /// Inflation rate extrinsic is missing an array of changes - ProjectsIsEmpty, + EB5MissingFeedback, + /// Inflation rate extrinsic is missing an array of project ids + InflationRateMissingProjectIds, /// Inflation rate was not provided InflationRateRequired, /// Bulkupload drawdowns are only allowed for Construction Loan & Developer Equity From ace213e72d7d6a97139fe9836605e3b645f4beda Mon Sep 17 00:00:00 2001 From: didiermis Date: Tue, 8 Nov 2022 17:07:31 -0600 Subject: [PATCH 3/8] Update pallet constants --- pallets/fund-admin/src/lib.rs | 15 --------------- pallets/fund-admin/src/mock.rs | 10 ---------- pallets/fund-admin/src/types.rs | 2 +- runtime/src/lib.rs | 12 +----------- 4 files changed, 2 insertions(+), 37 deletions(-) diff --git a/pallets/fund-admin/src/lib.rs b/pallets/fund-admin/src/lib.rs index 142fab47..98726aeb 100644 --- a/pallets/fund-admin/src/lib.rs +++ b/pallets/fund-admin/src/lib.rs @@ -54,27 +54,15 @@ pub mod pallet { type RemoveOrigin: EnsureOrigin; - #[pallet::constant] - type ProjectNameMaxLen: Get; - - #[pallet::constant] - type ProjectDescMaxLen: Get; - #[pallet::constant] type MaxDocuments: Get; - #[pallet::constant] - type MaxAccountsPerTransaction: Get; - #[pallet::constant] type MaxProjectsPerUser: Get; #[pallet::constant] type MaxUserPerProject: Get; - #[pallet::constant] - type CIDMaxLen: Get; - #[pallet::constant] type MaxBuildersPerProject: Get; @@ -99,9 +87,6 @@ pub mod pallet { #[pallet::constant] type MaxRegistrationsAtTime: Get; - #[pallet::constant] - type MaxDrawdownsByStatus: Get; - #[pallet::constant] type MaxExpendituresPerProject: Get; diff --git a/pallets/fund-admin/src/mock.rs b/pallets/fund-admin/src/mock.rs index af3578b4..11a36447 100644 --- a/pallets/fund-admin/src/mock.rs +++ b/pallets/fund-admin/src/mock.rs @@ -58,12 +58,8 @@ impl system::Config for Test { } parameter_types! { - pub const ProjectNameMaxLen:u32 = 32; - pub const ProjectDescMaxLen:u32 = 256; pub const MaxDocuments:u32 = 5; - pub const MaxAccountsPerTransaction:u32 = 5; pub const MaxProjectsPerUser:u32 = 10; - pub const CIDMaxLen:u32 = 100; pub const MaxUserPerProject:u32 = 50; pub const MaxBuildersPerProject:u32 = 1; pub const MaxInvestorsPerProject:u32 = 50; @@ -73,7 +69,6 @@ parameter_types! { pub const MaxDrawdownsPerProject:u32 = 1000; pub const MaxTransactionsPerDrawdown:u32 = 500; pub const MaxRegistrationsAtTime:u32 = 50; - pub const MaxDrawdownsByStatus:u32 = 2000; pub const MaxExpendituresPerProject:u32 = 1000; } @@ -81,12 +76,8 @@ parameter_types! { impl pallet_fund_admin::Config for Test { type Event = Event; type RemoveOrigin = EnsureRoot; - type ProjectNameMaxLen = ProjectNameMaxLen; - type ProjectDescMaxLen = ProjectDescMaxLen; type MaxDocuments = MaxDocuments; - type MaxAccountsPerTransaction = MaxAccountsPerTransaction; type MaxProjectsPerUser = MaxProjectsPerUser; - type CIDMaxLen = CIDMaxLen; type MaxUserPerProject = MaxUserPerProject; type MaxBuildersPerProject = MaxBuildersPerProject; type MaxInvestorsPerProject = MaxInvestorsPerProject; @@ -96,7 +87,6 @@ impl pallet_fund_admin::Config for Test { type MaxDrawdownsPerProject = MaxDrawdownsPerProject; type MaxTransactionsPerDrawdown = MaxTransactionsPerDrawdown; type MaxRegistrationsAtTime = MaxRegistrationsAtTime; - type MaxDrawdownsByStatus = MaxDrawdownsByStatus; type MaxExpendituresPerProject = MaxExpendituresPerProject; diff --git a/pallets/fund-admin/src/types.rs b/pallets/fund-admin/src/types.rs index 6ccaf332..037a87f2 100644 --- a/pallets/fund-admin/src/types.rs +++ b/pallets/fund-admin/src/types.rs @@ -6,7 +6,7 @@ use sp_runtime::sp_std::vec::Vec; //TODO: Fix types when using an Option, i.e: Option pub type FieldName = BoundedVec>; pub type FieldDescription = BoundedVec>; -pub type CID = BoundedVec>; +pub type CID = BoundedVec>; pub type Documents = BoundedVec<(FieldName,CID), ::MaxDocuments>; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 90795753..f1fce6f5 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -555,12 +555,8 @@ impl pallet_fruniques::Config for Runtime { } parameter_types! { - pub const ProjectNameMaxLen:u32 = 32; - pub const ProjectDescMaxLen:u32 = 256; pub const MaxDocuments:u32 = 5; - pub const MaxAccountsPerTransaction:u32 = 5; pub const MaxProjectsPerUser:u32 = 10; - pub const CIDMaxLen:u32 = 100; pub const MaxUserPerProject:u32 = 50; pub const MaxBuildersPerProject:u32 = 1; pub const MaxInvestorsPerProject:u32 = 50; @@ -570,7 +566,6 @@ parameter_types! { pub const MaxDrawdownsPerProject:u32 = 1000; pub const MaxTransactionsPerDrawdown:u32 = 500; pub const MaxRegistrationsAtTime:u32 = 50; - pub const MaxDrawdownsByStatus:u32 = 2000; pub const MaxExpendituresPerProject:u32 = 1000; } @@ -584,13 +579,9 @@ impl pallet_fund_admin::Config for Runtime { EnsureRoot, pallet_collective::EnsureProportionAtLeast, >; - - type ProjectNameMaxLen = ProjectNameMaxLen; - type ProjectDescMaxLen = ProjectDescMaxLen; + type MaxDocuments = MaxDocuments; - type MaxAccountsPerTransaction = MaxAccountsPerTransaction; type MaxProjectsPerUser = MaxProjectsPerUser; - type CIDMaxLen = CIDMaxLen; type MaxUserPerProject = MaxUserPerProject; type MaxBuildersPerProject = MaxBuildersPerProject; type MaxInvestorsPerProject = MaxInvestorsPerProject; @@ -600,7 +591,6 @@ impl pallet_fund_admin::Config for Runtime { type MaxDrawdownsPerProject = MaxDrawdownsPerProject; type MaxTransactionsPerDrawdown = MaxTransactionsPerDrawdown; type MaxRegistrationsAtTime = MaxRegistrationsAtTime; - type MaxDrawdownsByStatus = MaxDrawdownsByStatus; type MaxExpendituresPerProject = MaxExpendituresPerProject; } From 5d395420a73786453118757907a556973095e585 Mon Sep 17 00:00:00 2001 From: didiermis Date: Tue, 8 Nov 2022 21:57:11 -0600 Subject: [PATCH 4/8] Add validation to ensure only investor role can update or edit their documents. --- pallets/fund-admin/src/functions.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pallets/fund-admin/src/functions.rs b/pallets/fund-admin/src/functions.rs index 20b20e0a..ba27d4a4 100644 --- a/pallets/fund-admin/src/functions.rs +++ b/pallets/fund-admin/src/functions.rs @@ -399,7 +399,8 @@ impl Pallet { }, CUDAction::Delete => { // Ensure admin cannot delete themselves - ensure!(user.0 != admin, Error::::AdministatorsCannotDeleteThemselves); + ensure!(user.0 != admin, Error::::AdministratorsCannotDeleteThemselves, + ); Self::do_delete_user( user.0.clone() @@ -545,6 +546,8 @@ impl Pallet { user_info.email = mod_email[0].clone(); } if let Some(documents) = documents { + // Ensure user is an investor + ensure!(user_info.role == ProxyRole::Investor, Error::::UserIsNotAnInvestor); user_info.documents = Some(documents); } Ok(()) From 5b209d01a3b8c82d525c6006b61bf6d6e0178a04 Mon Sep 17 00:00:00 2001 From: didiermis Date: Tue, 8 Nov 2022 21:58:03 -0600 Subject: [PATCH 5/8] Add unit tests for administrator registration flow. --- pallets/fund-admin/src/tests.rs | 42 +++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/pallets/fund-admin/src/tests.rs b/pallets/fund-admin/src/tests.rs index 3c8cc47c..a1a1b430 100644 --- a/pallets/fund-admin/src/tests.rs +++ b/pallets/fund-admin/src/tests.rs @@ -145,6 +145,48 @@ fn sudo_cannot_delete_an_administrator_account_that_doesnt_exist_shouldnt_work() }); } +#[test] +fn sudo_administrator_can_remove_another_administrator_account_works() { + new_test_ext().execute_with(|| { + assert_ok!(FundAdmin::sudo_add_administrator( + Origin::root(), + 2, + return_field_name("Alice Keys") + )); + assert!(FundAdmin::users_info(2).is_some()); + + assert_ok!(FundAdmin::sudo_add_administrator( + Origin::root(), + 3, + return_field_name("Bob Keys") + )); + assert!(FundAdmin::users_info(3).is_some()); + + assert_ok!(FundAdmin::sudo_remove_administrator( + Origin::root(), + 2, + )); + assert!(FundAdmin::users_info(2).is_none()); + }); +} + +#[test] +fn sudo_administrator_can_remove_themselves_works() { + new_test_ext().execute_with(|| { + assert_ok!(FundAdmin::sudo_add_administrator( + Origin::root(), + 2, + return_field_name("Alice Keys") + )); + assert!(FundAdmin::users_info(2).is_some()); + + assert_ok!(FundAdmin::sudo_remove_administrator( + Origin::root(), + 2, + )); + assert!(FundAdmin::users_info(2).is_none()); + }); +} // U S E R S // ----------------------------------------------------------------------------------------- From 2c7559e2a1773a2853621868d4c427cdb1337c67 Mon Sep 17 00:00:00 2001 From: didiermis Date: Tue, 8 Nov 2022 21:58:27 -0600 Subject: [PATCH 6/8] Fix type. Update internal documentation. --- pallets/fund-admin/src/lib.rs | 134 +++++++++++++++++++++++++++++----- 1 file changed, 117 insertions(+), 17 deletions(-) diff --git a/pallets/fund-admin/src/lib.rs b/pallets/fund-admin/src/lib.rs index 98726aeb..421a5f2b 100644 --- a/pallets/fund-admin/src/lib.rs +++ b/pallets/fund-admin/src/lib.rs @@ -14,11 +14,8 @@ mod benchmarking; mod functions; mod types; //TODO: Remobe unused parameters, types, etc used for development -// - Remove unused constants // - Change extrinsic names // - Update extrinsic names to beign like CURD actions ( create, update, read, delete) -// - Remove unused pallet errors -// - Remove unused pallet events // - Add internal documentation for each extrinsic // - Add external documentation for each extrinsic // - Fix typos @@ -89,8 +86,7 @@ pub mod pallet { #[pallet::constant] type MaxExpendituresPerProject: Get; - - + } #[pallet::pallet] @@ -225,9 +221,9 @@ pub mod pallet { ProjectEdited([u8;32]), /// Project was deleted ProjectDeleted([u8;32]), - /// Administator added + /// Administrator added AdministratorAssigned(T::AccountId), - /// Administator removed + /// Administrator removed AdministratorRemoved(T::AccountId), /// Users has been assigned from the selected project UsersAssignationCompleted([u8;32]), @@ -267,9 +263,6 @@ pub mod pallet { // ------------------------------------------------------------------------------------------------------------ #[pallet::error] pub enum Error { - /// Error names should be descriptive. - /// TODO: map each constant type used by bounded vecs to a pallet error - /// when boundaries are exceeded /// No value was found for the global scope NoGlobalScopeValueWasFound, /// Project ID is already in use @@ -296,7 +289,7 @@ pub mod pallet { MaxProjectsPerUserReached, /// User is not assigned to the project UserNotAssignedToProject, - /// Can not register administator role + /// Can not register administrator role CannotRegisterAdminRole, /// Max number of builders per project reached MaxBuildersPerProjectReached, @@ -306,7 +299,7 @@ pub mod pallet { MaxIssuersPerProjectReached, /// Max number of regional centers per project reached MaxRegionalCenterPerProjectReached, - /// Can not remove administator role + /// Can not remove administrator role CannotRemoveAdminRole, /// Can not add admin role at user project assignment CannotAddAdminRole, @@ -382,8 +375,8 @@ pub mod pallet { NoTransactionsToSubmit, /// Bulk upload description is required BulkUploadDescriptionRequired, - /// Administator can not delete themselves - AdministatorsCannotDeleteThemselves, + /// Administrator can not delete themselves + AdministratorsCannotDeleteThemselves, /// No feedback was provided for bulk upload NoFeedbackProvidedForBulkUpload, /// NO feedback for EN5 drawdown was provided @@ -400,6 +393,8 @@ pub mod pallet { UserHasAssignedProjectsCannotDelete, /// Cannot send a bulkupload drawdown if the drawdown status isn't in draft or rejected DrawdownStatusNotSupportedForBulkUpload, + /// Only investors can update/edit their documents + UserIsNotAnInvestor, } @@ -409,6 +404,12 @@ pub mod pallet { impl Pallet { // I N I T I A L // -------------------------------------------------------------------------------------------- + /// Initialize the pallet by setting the permissions for each role + /// & the global scope + /// + /// # Considerations: + /// - This function can only be called once + /// - This function can only be called usinf the sudo pallet #[transactional] #[pallet::weight(10_000 + T::DbWeight::get().writes(10))] pub fn initial_setup( @@ -419,6 +420,17 @@ pub mod pallet { Ok(()) } + /// Adds an administrator account to the site + /// + /// # Parameters: + /// - origin: The sudo account + /// - admin: The administrator account to be added + /// - name: The name of the administrator account + /// + /// # Considerations: + /// - This function can only be called using the sudo pallet + /// - This function is used to add the first administrator to the site + /// - If the user is already registered, the function will return an error: UserAlreadyRegistered #[transactional] #[pallet::weight(10_000 + T::DbWeight::get().writes(10))] pub fn sudo_add_administrator( @@ -431,6 +443,20 @@ pub mod pallet { Ok(()) } + /// Removes an administrator account from the site + /// + /// # Parameters: + /// - origin: The sudo account + /// - admin: The administrator account to be removed + /// + /// # Considerations: + /// - This function can only be called using the sudo pallet + /// - This function is used to remove any administrator from the site + /// - If the user is not registered, the function will return an error: UserNotFound + /// + /// # Note: + /// WARNING: Administrators can remove themselves from the site, + /// but they can add themselves back #[transactional] #[pallet::weight(10_000 + T::DbWeight::get().writes(10))] pub fn sudo_remove_administrator( @@ -445,6 +471,32 @@ pub mod pallet { // U S E R S // -------------------------------------------------------------------------------------------- + /// This extrinsic is used to register, update, or delete a user account + /// + /// # Parameters: + /// - origin: The administrator account + /// - user: The target user account to be registered, updated, or deleted. + /// It is an array of user accounts where each entry it should be a tuple of the following: + /// - 0: The user account + /// - 1: The user name + /// - 2: The user role + /// - 3: The CUD operation to be performed on the user account. CUD action is ALWAYS required. + /// + /// # Considerations: + /// - Users parameters are optional because depends on the CUD action as follows: + /// * **Create**: The user account, user name, user role & CUD action are required + /// * **Update**: The user account & CUD action are required. The user name & user role are optionals. + /// * **Delete**: The user account & CUD action are required. + /// - This function can only be called by an administrator account + /// - Multiple users can be registered, updated, or deleted at the same time, but + /// the user account must be unique. Multiple actions over the same user account + /// in the same call will result in an unexpected behavior. + /// - If the user is already registered, the function will return an error: UserAlreadyRegistered + /// - If the user is not registered, the function will return an error: UserNotFound + /// + /// # Note: + /// WARNING: It is possible to register, update, or delete administators accounts using this extrinsic, + /// but administrators can not delete themselves. #[transactional] #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn users( @@ -461,23 +513,71 @@ pub mod pallet { Self::do_execute_users(who, users) } + /// Edits an user account + /// + /// # Parameters: + /// - origin: The user account which is being edited + /// - name: The name of the user account which is being edited + /// - image: The image of the user account which is being edited + /// - email: The email of the user account which is being edited + /// - documents: The documents of the user account which is being edited. + /// ONLY available for the investor role. + /// + /// + /// # Considerations: + /// - If the user is not registered, the function will return an error: UserNotFound + /// - This function can only be called by a registered user account + /// - This function will be called by the user account itself + /// - ALL parameters are optional because depends on what is being edited + /// - ONLY the investor role can edit or update the documents #[transactional] #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn users_edit_user( origin: OriginFor, - user: T::AccountId, name: Option>, image: Option>, email: Option>, documents: Option> ) -> DispatchResult { - let _who = ensure_signed(origin)?; + let who = ensure_signed(origin)?; - Self::do_edit_user(user, name, image, email, documents) + Self::do_edit_user(who, name, image, email, documents) } // P R O J E C T S // -------------------------------------------------------------------------------------------- + /// Registers a new project. + /// + /// # Parameters: + /// - origin: The administrator account + /// - title: The title of the project + /// - description: The description of the project + /// - image: The image of the project (CID) + /// - address: The address of the project + /// - creation_date: The creation date of the project + /// - completion_date: The completion date of the project + /// - expenditures: The expenditures of the project. It is an array of tuples where each entry + /// is a tuple of the following: + /// * 0: The expenditure name + /// * 1: The expenditure type + /// * 2: The expenditure amount + /// * 3: The expenditure NAICS code + /// * 4: The expenditure jobs multiplier + /// * 5: The CUD action to be performed on the expenditure. CUD action is ALWAYS required. + /// * 6: The expenditure id. It is optional because it is only required when updating or deleting + /// - users: The users who will be assigned to the project. It is an array of tuples where each entry + /// is a tuple of the following: + /// * 0: The user account + /// * 1: The user role + /// * 2: The AssignAction to be performed on the user. + /// + /// # Considerations: + /// - This function can only be called by an administrator account + /// - For users assignation, the user account must be registered. If the user is not registered, + /// the function will return an error. ALL parameters are required. + /// - For expenditures, apart from the expenditure id, naics code & jopbs multiplier, ALL parameters are required because for this + /// flow, the expenditures are always created. The naics code & the jobs multiplier + /// can be added later by the administrator. #[transactional] #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn projects_create_project( From d2710bdfd634c63934aeb224e24b7d1845970990 Mon Sep 17 00:00:00 2001 From: Erick Casanova Date: Wed, 9 Nov 2022 14:28:02 -0600 Subject: [PATCH 7/8] move verify functionality to be called by the root admin --- pallets/fruniques/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pallets/fruniques/src/lib.rs b/pallets/fruniques/src/lib.rs index 9d3b1f1a..d62e87c2 100644 --- a/pallets/fruniques/src/lib.rs +++ b/pallets/fruniques/src/lib.rs @@ -305,6 +305,7 @@ pub mod pallet { class_id: CollectionId, instance_id: ItemId, ) -> DispatchResult { + T::RemoveOrigin::ensure_origin(origin.clone())?; ensure!(Self::item_exists(&class_id, &instance_id), >::FruniqueNotFound); let owner: T::AccountId = ensure_signed(origin.clone())?; From 6692c4d53c1701704a9b8774dfb0ee613de0a412 Mon Sep 17 00:00:00 2001 From: Erick Casanova Date: Wed, 9 Nov 2022 14:42:38 -0600 Subject: [PATCH 8/8] Release 126 --- runtime/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 2a8c1b66..89be8bcc 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -106,7 +106,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 125, + spec_version: 126, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -579,7 +579,7 @@ impl pallet_fund_admin::Config for Runtime { EnsureRoot, pallet_collective::EnsureProportionAtLeast, >; - + type MaxDocuments = MaxDocuments; type MaxProjectsPerUser = MaxProjectsPerUser; type MaxUserPerProject = MaxUserPerProject;