diff --git a/Cargo.lock b/Cargo.lock index 0c35299c..b5c09466 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2681,9 +2681,9 @@ checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a" [[package]] name = "libc" -version = "0.2.97" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" +checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c" [[package]] name = "libloading" @@ -3838,7 +3838,7 @@ dependencies = [ [[package]] name = "pallet-process-validation" -version = "1.0.0" +version = "1.0.1" dependencies = [ "frame-benchmarking", "frame-support", @@ -7956,7 +7956,7 @@ checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" [[package]] name = "vitalam-node" -version = "2.8.1" +version = "2.8.2" dependencies = [ "bs58", "frame-benchmarking", @@ -7994,7 +7994,7 @@ dependencies = [ [[package]] name = "vitalam-node-runtime" -version = "2.2.0" +version = "2.2.1" dependencies = [ "frame-benchmarking", "frame-executive", diff --git a/node/Cargo.toml b/node/Cargo.toml index 68fb6153..f6616a6f 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -6,7 +6,7 @@ edition = '2018' license = 'Apache-2.0' repository = 'https://github.com/digicatapult/vitalam-node/' name = 'vitalam-node' -version = '2.8.1' +version = '2.8.2' [[bin]] name = 'vitalam-node' @@ -25,7 +25,7 @@ structopt = '0.3.8' hex-literal = "0.3.1" bs58 = "0.4.0" -vitalam-node-runtime = { path = '../runtime', version = '2.2.0' } +vitalam-node-runtime = { path = '../runtime', version = '2.2.1' } # Substrate dependencies frame-benchmarking = '3.0.0' diff --git a/pallets/process-validation/Cargo.toml b/pallets/process-validation/Cargo.toml index a9113489..6b1e1edd 100644 --- a/pallets/process-validation/Cargo.toml +++ b/pallets/process-validation/Cargo.toml @@ -5,7 +5,7 @@ edition = '2018' license = 'Apache-2.0' repository = 'https://github.com/digicatapult/vitalam-node/' name = 'pallet-process-validation' -version = "1.0.0" +version = "1.0.1" [package.metadata.docs.rs] diff --git a/pallets/process-validation/src/lib.rs b/pallets/process-validation/src/lib.rs index d66c8688..17d13d8c 100644 --- a/pallets/process-validation/src/lib.rs +++ b/pallets/process-validation/src/lib.rs @@ -1,7 +1,9 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::{Decode, Encode}; +use frame_support::Parameter; pub use pallet::*; +use sp_runtime::traits::{AtLeast32Bit, One}; use sp_std::prelude::*; use vitalam_pallet_traits::{ProcessIO, ProcessValidator}; @@ -21,6 +23,7 @@ use restrictions::Restriction; pub enum ProcessStatus { Disabled, Enabled, + None, } impl Default for ProcessStatus { @@ -29,15 +32,23 @@ impl Default for ProcessStatus { } } -#[derive(Encode, Decode, Default, Clone, PartialEq)] +#[derive(Encode, Decode, Clone, PartialEq)] #[cfg_attr(feature = "std", derive(Debug))] pub struct Process { status: ProcessStatus, restrictions: Vec, } -pub mod weights; +impl Default for Process { + fn default() -> Self { + Process { + status: ProcessStatus::None, + restrictions: vec![], + } + } +} +pub mod weights; pub use weights::WeightInfo; #[frame_support::pallet] @@ -46,17 +57,17 @@ pub mod pallet { use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; - use sp_runtime::traits::AtLeast32Bit; + + type Restrictions = Vec; /// The pallet's configuration trait. #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. type Event: From> + IsType<::Event>; - - // The primary identifier for a process (i.e. it's name) + // The primary identifier for a process (i.e. it's name, and version) type ProcessIdentifier: Parameter; - type ProcessVersion: Parameter + AtLeast32Bit; + type ProcessVersion: Parameter + AtLeast32Bit + Default; // Origins for calling these extrinsics. For now these are expected to be root type CreateProcessOrigin: EnsureOrigin; @@ -79,8 +90,8 @@ pub mod pallet { /// Storage map definition #[pallet::storage] - #[pallet::getter(fn processes_by_id_and_version)] - pub(super) type ProcessesByIdAndVersion = StorageDoubleMap< + #[pallet::getter(fn process_model)] + pub(super) type ProcessModel = StorageDoubleMap< _, Blake2_128Concat, T::ProcessIdentifier, @@ -90,38 +101,142 @@ pub mod pallet { ValueQuery, >; + #[pallet::storage] + #[pallet::getter(fn version_model)] + pub(super) type VersionModel = + StorageMap<_, Blake2_128Concat, T::ProcessIdentifier, T::ProcessVersion, ValueQuery>; + #[pallet::event] + #[pallet::metadata( + ProcessIdentifier = "ProcessIdentifier", + ProcessVersion = "ProcessVersion", + Vec = "Restrictions", + bool = "IsNew" + )] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - // TODO: implement correct events for extrinsics including params - ProcessCreated, - ProcessDisabled, + // id, version, restrictions, is_new + ProcessCreated(T::ProcessIdentifier, T::ProcessVersion, Vec, bool), + //id, version + ProcessDisabled(T::ProcessIdentifier, T::ProcessVersion), } #[pallet::error] pub enum Error { - // TODO: implement errors for extrinsics + // process already exists, investigate + AlreadyExists, + // attempting to disable non-existing process + NonExistingProcess, + // process is already disabled + AlreadyDisabled, + // process not found for this versiion + InvalidVersion, } // The pallet's dispatchable functions. #[pallet::call] impl Pallet { - // TODO: implement create_process with correct parameters and impl #[pallet::weight(T::WeightInfo::create_process())] - pub(super) fn create_process(origin: OriginFor) -> DispatchResultWithPostInfo { - // Check it was signed and get the signer + pub(super) fn create_process( + origin: OriginFor, + id: T::ProcessIdentifier, + restrictions: Vec, + ) -> DispatchResultWithPostInfo { T::CreateProcessOrigin::ensure_origin(origin)?; + let version: T::ProcessVersion = Pallet::::update_version(id.clone()).unwrap(); + Pallet::::persist_process(&id, &version, &restrictions)?; + + Self::deposit_event(Event::ProcessCreated( + id, + version.clone(), + restrictions, + version == One::one(), + )); - Self::deposit_event(Event::ProcessCreated); - Ok(().into()) + return Ok(().into()); } - // TODO: implement disable_process with correct parameters and impl #[pallet::weight(T::WeightInfo::disable_process())] - pub(super) fn disable_process(origin: OriginFor) -> DispatchResultWithPostInfo { + pub(super) fn disable_process( + origin: OriginFor, + id: T::ProcessIdentifier, + version: T::ProcessVersion, + ) -> DispatchResultWithPostInfo { T::DisableProcessOrigin::ensure_origin(origin)?; - Self::deposit_event(Event::ProcessDisabled); - Ok(().into()) + Pallet::::validate_version_and_process(&id, &version)?; + Pallet::::set_disabled(&id, &version)?; + + Self::deposit_event(Event::ProcessDisabled(id, version)); + return Ok(().into()); + } + } + + // helper methods + impl Pallet { + pub fn get_version(id: &T::ProcessIdentifier) -> T::ProcessVersion { + return match >::contains_key(&id) { + true => >::get(&id) + One::one(), + false => One::one(), + }; + } + + pub fn update_version(id: T::ProcessIdentifier) -> Result> { + let version: T::ProcessVersion = Pallet::::get_version(&id); + match version == One::one() { + true => >::insert(&id, version.clone()), + false => >::mutate(&id, |v| *v = version.clone()), + }; + + return Ok(version); + } + + pub fn persist_process( + id: &T::ProcessIdentifier, + v: &T::ProcessVersion, + r: &Restrictions, + ) -> Result<(), Error> { + return match >::contains_key(&id, &v) { + true => Err(Error::::AlreadyExists), + false => { + >::insert( + id, + v, + Process { + restrictions: r.clone(), + ..Default::default() + }, + ); + return Ok(()); + } + }; + } + + pub fn set_disabled(id: &T::ProcessIdentifier, version: &T::ProcessVersion) -> Result<(), Error> { + let process: Process = >::get(&id, &version); + return match process.status == ProcessStatus::Disabled { + true => Err(Error::::AlreadyDisabled), + false => { + >::mutate(id.clone(), version, |process| { + (*process).status = ProcessStatus::Disabled; + }); + return Ok(()); + } + }; + } + + pub fn validate_version_and_process( + id: &T::ProcessIdentifier, + version: &T::ProcessVersion, + ) -> Result<(), Error> { + ensure!( + >::contains_key(&id, version.clone()), + Error::::NonExistingProcess, + ); + ensure!(>::contains_key(&id), Error::::InvalidVersion); + return match *version != >::get(&id) { + true => Err(Error::::InvalidVersion), + false => Ok(()), + }; } } } diff --git a/pallets/process-validation/src/tests.rs b/pallets/process-validation/src/tests.rs index 1cc9daf7..e4c9b413 100644 --- a/pallets/process-validation/src/tests.rs +++ b/pallets/process-validation/src/tests.rs @@ -28,6 +28,7 @@ frame_support::construct_runtime!( { System: frame_system::{Module, Call, Config, Storage, Event}, ProcessValidation: pallet_process_validation::{Module, Call, Storage, Event}, + } ); parameter_types! { diff --git a/pallets/process-validation/src/tests/create_process.rs b/pallets/process-validation/src/tests/create_process.rs index bb216d45..8bb61c89 100644 --- a/pallets/process-validation/src/tests/create_process.rs +++ b/pallets/process-validation/src/tests/create_process.rs @@ -1,10 +1,144 @@ use super::*; +use crate::Error; +use crate::Event::*; +use crate::{Process, ProcessModel, ProcessStatus, Restriction::None, VersionModel}; +use frame_support::{assert_noop, assert_ok, dispatch::DispatchError}; -use frame_support::assert_ok; +// -- fixtures -- +#[allow(dead_code)] +const PROCESS_ID1: [u8; 32] = [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; +const PROCESS_ID2: [u8; 32] = [ + 1, 2, 3, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; #[test] -fn create_process_simple() { +fn returns_error_if_origin_validation_fails_and_no_data_added() { new_test_ext().execute_with(|| { - assert_ok!(ProcessValidation::create_process(Origin::root())); + System::set_block_number(1); + assert_noop!( + ProcessValidation::create_process(Origin::none(), PROCESS_ID1, vec![{ None }]), + DispatchError::BadOrigin, + ); + assert_eq!(>::get(PROCESS_ID1), 0u32); + assert_eq!( + >::get(PROCESS_ID1, 1u32), + Process { + status: ProcessStatus::None, + restrictions: [].to_vec(), + } + ); + assert_eq!(System::events().len(), 0); + }); +} + +#[test] +fn handles_if_process_exists_for_the_new_version() { + new_test_ext().execute_with(|| { + >::insert( + PROCESS_ID1, + 1, + Process { + status: ProcessStatus::Disabled, + restrictions: vec![{ None }], + }, + ); + let result = ProcessValidation::create_process(Origin::root(), PROCESS_ID1, vec![{ None }]); + assert_noop!(result, Error::::AlreadyExists); + }); +} + +#[test] +fn if_no_version_found_it_should_return_default_and_insert_new_one() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + assert_eq!(>::get(PROCESS_ID1), 0u32); + assert_ok!(ProcessValidation::create_process( + Origin::root(), + PROCESS_ID1, + vec![{ None }], + )); + + let expected = Event::pallet_process_validation(ProcessCreated(PROCESS_ID1, 1u32, vec![{ None }], true)); + assert_eq!(System::events()[0].event, expected); + }); +} + +#[test] +fn for_existing_process_it_mutates_an_existing_version() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + assert_ok!(ProcessValidation::update_version(PROCESS_ID1)); + assert_ok!(ProcessValidation::update_version(PROCESS_ID1)); + assert_ok!(ProcessValidation::update_version(PROCESS_ID1)); + + let items: Vec = >::iter() + .map(|item: ([u8; 32], u32)| item.1.clone()) + .collect(); + + assert_eq!(items.len(), 1); + assert_eq!(items[0], 3); + assert_eq!(>::get(PROCESS_ID1), 3u32); + }); +} + +#[test] +fn sets_versions_correctly_for_multiple_processes() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let mut ids: Vec<[u8; 32]> = [PROCESS_ID2; 10].to_vec(); + ids.extend([PROCESS_ID1; 15].to_vec()); + ids.iter().for_each(|id: &[u8; 32]| -> () { + assert_ok!(ProcessValidation::update_version(*id)); + }); + + let id1_expected = Event::pallet_process_validation(ProcessCreated(PROCESS_ID1, 16u32, vec![{ None }], false)); + assert_ok!(ProcessValidation::create_process( + Origin::root(), + PROCESS_ID1, + vec![{ None }], + )); + let id2_expected = Event::pallet_process_validation(ProcessCreated(PROCESS_ID2, 11u32, vec![{ None }], false)); + assert_ok!(ProcessValidation::create_process( + Origin::root(), + PROCESS_ID2, + vec![{ None }], + )); + + assert_eq!(System::events()[0].event, id1_expected); + assert_eq!(System::events()[1].event, id2_expected); + }); +} + +#[test] +fn updates_version_correctly_for_existing_proces_and_dispatches_event() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + >::insert(PROCESS_ID1, 9u32); + let expected = Event::pallet_process_validation(ProcessCreated(PROCESS_ID1, 10u32, vec![{ None }], false)); + assert_ok!(ProcessValidation::create_process( + Origin::root(), + PROCESS_ID1, + vec![{ None }], + )); + assert_eq!(>::get(PROCESS_ID1), 10u32); + assert_eq!(System::events()[0].event, expected); + }); +} + +#[test] +fn updates_version_correctly_for_new_process_and_dispatches_event() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + assert_ok!(ProcessValidation::create_process( + Origin::root(), + PROCESS_ID1, + vec![{ None }], + )); + let expected = Event::pallet_process_validation(ProcessCreated(PROCESS_ID1, 1u32, vec![{ None }], true)); + // sets version to 1 and returns true to identify that this is a new event + assert_eq!(>::get(PROCESS_ID1), 1u32); + assert_eq!(System::events()[0].event, expected); }); } diff --git a/pallets/process-validation/src/tests/disable_process.rs b/pallets/process-validation/src/tests/disable_process.rs index 1ec4ce83..5cf8f52f 100644 --- a/pallets/process-validation/src/tests/disable_process.rs +++ b/pallets/process-validation/src/tests/disable_process.rs @@ -1,10 +1,73 @@ use super::*; +use crate::Error; +use crate::Event::*; +use crate::{Process, ProcessModel, ProcessStatus, Restriction::None, VersionModel}; +use frame_support::{assert_noop, assert_ok, dispatch::DispatchError}; -use frame_support::assert_ok; +const PROCESS_ID: [u8; 32] = [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; #[test] -fn create_process_simple() { +fn returns_error_if_origin_validation_fails_and_no_data_added() { new_test_ext().execute_with(|| { - assert_ok!(ProcessValidation::disable_process(Origin::root())); + System::set_block_number(1); + assert_noop!( + ProcessValidation::disable_process(Origin::none(), PROCESS_ID, 1u32), + DispatchError::BadOrigin, + ); + assert_eq!(System::events().len(), 0); + }); +} + +#[test] +fn returns_error_if_process_does_not_exist() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + assert_noop!( + ProcessValidation::disable_process(Origin::root(), PROCESS_ID, 1u32), + Error::::NonExistingProcess, + ); + assert_eq!(System::events().len(), 0); + }); +} + +#[test] +fn returns_error_if_process_is_already_disabled() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + >::insert(PROCESS_ID, 1u32); + >::insert( + PROCESS_ID, + 1u32, + Process { + status: ProcessStatus::Disabled, + restrictions: [{ None }].to_vec(), + }, + ); + assert_noop!( + ProcessValidation::disable_process(Origin::root(), PROCESS_ID, 1), + Error::::AlreadyDisabled, + ); + assert_eq!(System::events().len(), 0); + }); +} + +#[test] +fn disables_process_and_dispatches_event() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + >::insert(PROCESS_ID, 1u32); + >::insert( + PROCESS_ID, + 1u32, + Process { + status: ProcessStatus::Enabled, + restrictions: [{ None }].to_vec(), + }, + ); + assert_ok!(ProcessValidation::disable_process(Origin::root(), PROCESS_ID, 1u32,)); + let expected = Event::pallet_process_validation(ProcessDisabled(PROCESS_ID, 1)); + assert_eq!(System::events()[0].event, expected); }); } diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 9b5c97c2..3f25b7c3 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -4,7 +4,7 @@ edition = '2018' license = 'Apache-2.0' repository = 'https://github.com/digicatapult/vitalam-node/' name = 'vitalam-node-runtime' -version = '2.2.0' +version = '2.2.1' [package.metadata.docs.rs] targets = ['x86_64-unknown-linux-gnu'] @@ -26,7 +26,7 @@ serde = { features = ['derive'], optional = true, version = '1.0.101' } # local dependencies pallet-simple-nft = { version = '2.1.2', default-features = false, package = 'pallet-simple-nft', path = '../pallets/simple-nft' } -pallet-process-validation = { version = '1.0.0', default-features = false, package = 'pallet-process-validation', path = '../pallets/process-validation' } +pallet-process-validation = { version = '1.0.1', default-features = false, package = 'pallet-process-validation', path = '../pallets/process-validation' } pallet-symmetric-key = { version = '1.0.1', default-features = false, package = 'pallet-symmetric-key', path = '../pallets/symmetric-key' } frame-benchmarking = { default-features = false, optional = true, version = '3.0.0' } frame-executive = { default-features = false, version = '3.0.0' } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index d5bec87b..e4da1fea 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -93,7 +93,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("vitalam-node"), impl_name: create_runtime_str!("vitalam-node"), authoring_version: 1, - spec_version: 220, + spec_version: 221, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1,