diff --git a/Cargo.lock b/Cargo.lock index 457a9307e0..8c21cfdfcc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -968,7 +968,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "joystream-node-runtime" -version = "6.0.0" +version = "6.1.0" dependencies = [ "parity-scale-codec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index c69f0fe86a..8eaff91968 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ edition = '2018' name = 'joystream-node-runtime' # Follow convention: https://github.com/Joystream/substrate-runtime-joystream/issues/1 # {Authoring}.{Spec}.{Impl} of the RuntimeVersion -version = '6.0.0' +version = '6.1.0' [features] default = ['std'] diff --git a/src/content_working_group/lib.rs b/src/content_working_group/lib.rs index 0a49f03c84..cfe5f1ee40 100755 --- a/src/content_working_group/lib.rs +++ b/src/content_working_group/lib.rs @@ -225,8 +225,10 @@ pub static MSG_APPLY_ON_CURATOR_OPENING_UNSIGNED_ORIGIN: &str = "Unsigned origin pub static MSG_APPLY_ON_CURATOR_OPENING_MEMBER_ID_INVALID: &str = "Member id is invalid"; pub static MSG_APPLY_ON_CURATOR_OPENING_SIGNER_NOT_CONTROLLER_ACCOUNT: &str = "Signer does not match controller account"; -static MSG_ORIGIN_IS_NIETHER_MEMBER_CONTROLLER_OR_ROOT: &str = +pub static MSG_ORIGIN_IS_NIETHER_MEMBER_CONTROLLER_OR_ROOT: &str = "Origin must be controller or root account of member"; +pub static MSG_MEMBER_HAS_ACTIVE_APPLICATION_ON_OPENING: &str = + "Member already has an active application on the opening"; /// The exit stage of a lead involvement in the working group. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] @@ -1726,6 +1728,12 @@ decl_module! { hiring::Module::::ensure_can_add_application(curator_opening.opening_id, opt_role_stake_balance, opt_application_stake_balance) )?; + // Ensure member does not have an active application to this opening + Self::ensure_member_has_no_active_application_on_opening( + curator_opening.curator_applications, + member_id + )?; + // // == MUTATION SAFE == // @@ -2055,6 +2063,27 @@ impl versioned_store_permissions::CredentialChecker for Module { } impl Module { + fn ensure_member_has_no_active_application_on_opening( + curator_applications: CuratorApplicationIdSet, + member_id: T::MemberId, + ) -> Result<(), &'static str> { + for curator_application_id in curator_applications { + let curator_application = CuratorApplicationById::::get(curator_application_id); + // Look for application by the member for the opening + if curator_application.member_id != member_id { + continue; + } + // Get application details + let application = >::get(curator_application.application_id); + // Return error if application is in active stage + if application.stage == hiring::ApplicationStage::Active { + return Err(MSG_MEMBER_HAS_ACTIVE_APPLICATION_ON_OPENING); + } + } + // Member does not have any active applications to the opening + Ok(()) + } + fn ensure_can_register_role_on_member( member_id: &T::MemberId, role: role_types::Role, diff --git a/src/content_working_group/tests.rs b/src/content_working_group/tests.rs index 9100557759..66ab124a2a 100644 --- a/src/content_working_group/tests.rs +++ b/src/content_working_group/tests.rs @@ -8,7 +8,7 @@ use hiring; use rstd::collections::btree_map::BTreeMap; use rstd::collections::btree_set::BTreeSet; use runtime_primitives::traits::One; -use srml_support::{StorageLinkedMap, StorageValue}; +use srml_support::{assert_err, assert_ok, StorageLinkedMap, StorageValue}; /// DIRTY IMPORT BECAUSE /// InputValidationLengthConstraint has not been factored out yet!!! @@ -747,6 +747,78 @@ fn apply_on_curator_opening_success() { }); } +#[test] +fn multiple_applications_by_same_member_to_opening_fails() { + TestExternalitiesBuilder::::default() + .build() + .execute_with(|| { + /* + * Setup + */ + + let normal_opening_constructed = setup_normal_accepting_opening(); + + // Add curator membership + + let curator_applicant_root_and_controller_account = 72618; + + let curator_applicant_member_id = add_member( + curator_applicant_root_and_controller_account, + to_vec("IwillTrytoapplyhere"), + ); + + let curator_applicant_role_account = 8881111; + + let role_stake_balance = get_baseline_opening_policy() + .role_staking_policy + .unwrap() + .amount; + let application_stake_balance = get_baseline_opening_policy() + .application_staking_policy + .unwrap() + .amount; + let total_balance = role_stake_balance + application_stake_balance; + + let source_account = curator_applicant_root_and_controller_account; + + // Credit staking source account with enough funds for two applications, + // because we don't want our second application to fail for lack of funds + let _ = balances::Module::::deposit_creating(&source_account, total_balance * 2); + + let human_readable_text = generate_valid_length_buffer(&ChannelHandleConstraint::get()); + + /* + * Test + */ + + // First application should work + assert_ok!(ContentWorkingGroup::apply_on_curator_opening( + Origin::signed(curator_applicant_root_and_controller_account), + curator_applicant_member_id, + normal_opening_constructed.curator_opening_id, + curator_applicant_role_account, + Some(role_stake_balance), + Some(application_stake_balance), + human_readable_text.clone() + )); + + // Second application should fail since + // first application is still active + assert_err!( + ContentWorkingGroup::apply_on_curator_opening( + Origin::signed(curator_applicant_root_and_controller_account), + curator_applicant_member_id, + normal_opening_constructed.curator_opening_id, + curator_applicant_role_account, + Some(role_stake_balance), + Some(application_stake_balance), + human_readable_text + ), + MSG_MEMBER_HAS_ACTIVE_APPLICATION_ON_OPENING + ); + }); +} + struct UpdateCuratorRoleAccountFixture { pub origin: Origin, pub member_id: ::MemberId, diff --git a/src/lib.rs b/src/lib.rs index fa9d8f37bd..893376501d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -118,7 +118,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("joystream-node"), impl_name: create_runtime_str!("joystream-node"), authoring_version: 6, - spec_version: 0, + spec_version: 1, impl_version: 0, apis: RUNTIME_API_VERSIONS, };