Browse Source

runtime: working-group: Restore ‘worker storage’ feature.

Shamil Gadelshin 3 years ago
parent
commit
7222ddc9fe

+ 11 - 0
runtime-modules/working-group/src/checks.rs

@@ -263,3 +263,14 @@ pub(crate) fn ensure_worker_has_recurring_reward<T: Trait<I>, I: Instance>(
         .reward_per_block
         .map_or(Err(Error::<T, I>::WorkerHasNoReward.into()), |_| Ok(()))
 }
+
+// Validates storage text.
+pub(crate) fn ensure_worker_role_storage_text_is_valid<T: Trait<I>, I: Instance>(
+    text: &[u8],
+) -> DispatchResult {
+    ensure!(
+        text.len() as u16 <= <crate::WorkerStorageSize>::get(),
+        Error::<T, I>::WorkerStorageValueTooLong
+    );
+    Ok(())
+}

+ 3 - 0
runtime-modules/working-group/src/errors.rs

@@ -86,5 +86,8 @@ decl_error! {
 
         /// Trying to fill opening with an application for other opening
         ApplicationsNotForOpening,
+
+        /// Worker storage text is too long.
+        WorkerStorageValueTooLong,
     }
 }

+ 48 - 0
runtime-modules/working-group/src/lib.rs

@@ -29,6 +29,11 @@
 // Do not delete! Cannot be uncommented by default, because of Parity decl_module! issue.
 //#![warn(missing_docs)]
 
+// TODO after Olympia-master (Sumer) merge:
+// - change module comment
+// - benchmark new extrinsics
+// - fix `ensure_worker_role_storage_text_is_valid` incorrect cast
+
 pub mod benchmarking;
 
 mod checks;
@@ -272,6 +277,12 @@ decl_event!(
         /// - Worker ID.
         /// - Missed reward (optional). None means 'no missed reward'.
         NewMissedRewardLevelReached(WorkerId, Option<Balance>),
+
+        /// Emits on updating the worker storage role.
+        /// Params:
+        /// - Id of the worker.
+        /// - Raw storage field.
+        WorkerStorageUpdated(WorkerId, Vec<u8>),
     }
 );
 
@@ -309,6 +320,13 @@ decl_storage! {
 
         /// Status text hash.
         pub StatusTextHash get(fn status_text_hash) : Vec<u8>;
+
+        /// Maps identifier to corresponding worker storage.
+        pub WorkerStorage get(fn worker_storage): map hasher(blake2_128_concat)
+            WorkerId<T> => Vec<u8>;
+
+        /// Worker storage size upper bound.
+        pub WorkerStorageSize get(fn worker_storage_size) : u16 = default_storage_size_constraint();
     }
 }
 
@@ -1102,6 +1120,31 @@ decl_module! {
             // Trigger event
             Self::deposit_event(RawEvent::BudgetSpending(account_id, amount, rationale));
         }
+
+        /// Update the associated role storage.
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn update_role_storage(
+            origin,
+            worker_id: WorkerId<T>,
+            storage: Vec<u8>
+        ) {
+
+            // Ensure there is a signer which matches role account of worker corresponding to provided id.
+            checks::ensure_worker_signed::<T,I>(origin, &worker_id)?;
+
+            // Ensure valid text.
+            checks::ensure_worker_role_storage_text_is_valid::<T,I>(&storage)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Complete the role storage update
+            WorkerStorage::<T, I>::insert(worker_id, storage.clone());
+
+            // Trigger event
+            Self::deposit_event(RawEvent::WorkerStorageUpdated(worker_id, storage));
+        }
     }
 }
 
@@ -1513,3 +1556,8 @@ impl<T: Trait<I>, I: Instance> common::working_group::WorkingGroupBudgetHandler<
         Self::set_working_group_budget(new_value);
     }
 }
+
+// Creates default storage size constraint.
+pub(crate) fn default_storage_size_constraint() -> u16 {
+    2048
+}

+ 37 - 1
runtime-modules/working-group/src/tests/fixtures.rs

@@ -7,7 +7,9 @@ use sp_runtime::traits::Hash;
 use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet};
 
 use super::hiring_workflow::HiringWorkflow;
-use super::mock::{Balances, LockId, System, Test, TestEvent, TestWorkingGroup};
+use super::mock::{
+    Balances, LockId, System, Test, TestEvent, TestWorkingGroup, DEFAULT_WORKER_ACCOUNT_ID,
+};
 use crate::types::StakeParameters;
 use crate::{
     Application, ApplyOnOpeningParameters, DefaultInstance, Opening, OpeningType, RawEvent,
@@ -1207,3 +1209,37 @@ impl SpendFromBudgetFixture {
         }
     }
 }
+
+pub struct UpdateWorkerStorageFixture {
+    worker_id: u64,
+    storage_field: Vec<u8>,
+    origin: RawOrigin<u64>,
+}
+
+impl UpdateWorkerStorageFixture {
+    pub fn default_with_storage_field(worker_id: u64, storage_field: Vec<u8>) -> Self {
+        Self {
+            worker_id,
+            storage_field,
+            origin: RawOrigin::Signed(DEFAULT_WORKER_ACCOUNT_ID),
+        }
+    }
+    pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
+        Self { origin, ..self }
+    }
+
+    pub fn call_and_assert(&self, expected_result: DispatchResult) {
+        let actual_result = TestWorkingGroup::update_role_storage(
+            self.origin.clone().into(),
+            self.worker_id,
+            self.storage_field.clone(),
+        );
+        assert_eq!(actual_result, expected_result);
+
+        if actual_result.is_ok() {
+            let storage = TestWorkingGroup::worker_storage(self.worker_id);
+
+            assert_eq!(storage, self.storage_field);
+        }
+    }
+}

+ 7 - 2
runtime-modules/working-group/src/tests/hiring_workflow.rs

@@ -5,7 +5,7 @@ use frame_system::RawOrigin;
 use crate::tests::fixtures::{
     AddOpeningFixture, ApplyOnOpeningFixture, FillOpeningFixture, HireLeadFixture,
 };
-use crate::tests::mock::{Test, TestWorkingGroup};
+use crate::tests::mock::{Test, TestWorkingGroup, DEFAULT_WORKER_ACCOUNT_ID};
 use crate::types::StakeParameters;
 use crate::{OpeningType, StakePolicy, Trait};
 
@@ -94,7 +94,12 @@ impl HiringWorkflow {
     }
 
     pub fn add_application(self, worker_handle: Vec<u8>) -> Self {
-        self.add_application_full(worker_handle, RawOrigin::Signed(2), 2, 2)
+        self.add_application_full(
+            worker_handle,
+            RawOrigin::Signed(DEFAULT_WORKER_ACCOUNT_ID),
+            2,
+            2,
+        )
     }
 
     pub fn add_application_full(

+ 1 - 0
runtime-modules/working-group/src/tests/mock.rs

@@ -301,6 +301,7 @@ impl common::membership::MemberOriginValidator<Origin, u64, u64> for () {
 
 pub type TestWorkingGroup = Module<Test, DefaultInstance>;
 
+pub const DEFAULT_WORKER_ACCOUNT_ID: u64 = 2;
 pub const STAKING_ACCOUNT_ID_NOT_BOUND_TO_MEMBER: u64 = 222;
 pub const STAKING_ACCOUNT_ID_FOR_CONFLICTING_STAKES: u64 = 333;
 

+ 109 - 3
runtime-modules/working-group/src/tests/mod.rs

@@ -2,14 +2,15 @@ mod fixtures;
 mod hiring_workflow;
 mod mock;
 
-pub use mock::{build_test_externalities, Test};
+pub use mock::{build_test_externalities, Test, DEFAULT_WORKER_ACCOUNT_ID};
 
 use frame_system::RawOrigin;
 
 use crate::tests::fixtures::{
     CancelOpeningFixture, DecreaseWorkerStakeFixture, IncreaseWorkerStakeFixture, SetBudgetFixture,
     SetStatusTextFixture, SlashWorkerStakeFixture, SpendFromBudgetFixture,
-    UpdateRewardAccountFixture, UpdateRewardAmountFixture, WithdrawApplicationFixture,
+    UpdateRewardAccountFixture, UpdateRewardAmountFixture, UpdateWorkerStorageFixture,
+    WithdrawApplicationFixture,
 };
 use crate::tests::hiring_workflow::HiringWorkflow;
 use crate::tests::mock::{
@@ -17,7 +18,8 @@ use crate::tests::mock::{
 };
 use crate::types::StakeParameters;
 use crate::{
-    DefaultInstance, Error, OpeningType, RawEvent, RewardPaymentType, StakePolicy, Trait, Worker,
+    default_storage_size_constraint, DefaultInstance, Error, OpeningType, RawEvent,
+    RewardPaymentType, StakePolicy, Trait, Worker,
 };
 use common::working_group::WorkingGroupAuthenticator;
 use fixtures::{
@@ -2505,3 +2507,107 @@ fn is_worker_account_id_works_correctly() {
         );
     });
 }
+
+#[test]
+fn update_worker_storage_succeeds() {
+    build_test_externalities().execute_with(|| {
+        /*
+           Events are not emitted on block 0.
+           So any dispatchable calls made during genesis block formation will have no events emitted.
+           https://substrate.dev/recipes/2-appetizers/4-events.html
+        */
+        run_to_block(1);
+
+        let storage_field = vec![0u8].repeat(10);
+
+        let worker_id = HireRegularWorkerFixture::default().hire();
+
+        let update_storage_fixture = UpdateWorkerStorageFixture::default_with_storage_field(
+            worker_id,
+            storage_field.clone(),
+        );
+
+        update_storage_fixture.call_and_assert(Ok(()));
+
+        EventFixture::assert_last_crate_event(RawEvent::WorkerStorageUpdated(
+            worker_id,
+            storage_field,
+        ));
+    });
+}
+
+#[test]
+fn update_worker_storage_by_leader_succeeds() {
+    build_test_externalities().execute_with(|| {
+        let storage_field = vec![0u8].repeat(10);
+
+        let leader_account_id = 1;
+        let worker_id = HireLeadFixture::default().hire_lead();
+
+        let update_storage_fixture = UpdateWorkerStorageFixture::default_with_storage_field(
+            worker_id,
+            storage_field.clone(),
+        )
+        .with_origin(RawOrigin::Signed(leader_account_id));
+
+        update_storage_fixture.call_and_assert(Ok(()));
+
+        let worker_storage = TestWorkingGroup::worker_storage(worker_id);
+
+        assert_eq!(storage_field, worker_storage);
+    });
+}
+
+#[test]
+fn update_worker_storage_fails_with_invalid_origin_signed_account() {
+    build_test_externalities().execute_with(|| {
+        let worker_id = HireRegularWorkerFixture::default().hire();
+        let invalid_account_id = 44;
+        let storage_field = vec![0u8].repeat(10);
+
+        let update_storage_fixture =
+            UpdateWorkerStorageFixture::default_with_storage_field(worker_id, storage_field)
+                .with_origin(RawOrigin::Signed(invalid_account_id));
+
+        update_storage_fixture.call_and_assert(Err(
+            Error::<Test, DefaultInstance>::SignerIsNotWorkerRoleAccount.into(),
+        ));
+    });
+}
+
+#[test]
+fn update_worker_storage_fails_with_invalid_worker_id() {
+    build_test_externalities().execute_with(|| {
+        let storage_field = vec![0u8].repeat(10);
+        HireRegularWorkerFixture::default().hire();
+
+        let invalid_worker_id = 111;
+
+        let update_storage_fixture = UpdateWorkerStorageFixture::default_with_storage_field(
+            invalid_worker_id,
+            storage_field.clone(),
+        );
+
+        update_storage_fixture.call_and_assert(Err(
+            Error::<Test, DefaultInstance>::WorkerDoesNotExist.into(),
+        ));
+    });
+}
+
+#[test]
+fn update_worker_storage_fails_with_too_long_text() {
+    build_test_externalities().execute_with(|| {
+        let storage_field = vec![0u8].repeat(default_storage_size_constraint() as usize + 1);
+
+        let worker_id = HireRegularWorkerFixture::default().hire();
+
+        let update_storage_fixture = UpdateWorkerStorageFixture::default_with_storage_field(
+            worker_id,
+            storage_field.clone(),
+        );
+
+        update_storage_fixture.call_and_assert(Err(
+            Error::<Test, DefaultInstance>::WorkerStorageValueTooLong.into(),
+        ));
+    });
+}