diff --git a/Cargo.lock b/Cargo.lock index df5b107c..adcc3953 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7939,7 +7939,7 @@ checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" [[package]] name = "vitalam-node" -version = "2.3.0" +version = "2.4.0" dependencies = [ "bs58", "frame-benchmarking", diff --git a/README.md b/README.md index 6b0d48e3..44cc8357 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ Then you can run the benchmark tool with for example ./target/release/vitalam-node benchmark \ --pallet 'pallet_simple_nft' \ --extrinsic '*' \ + --repeat 1000 \ --output ./weights/ ``` @@ -104,11 +105,12 @@ In order to use the API within `polkadot.js` you'll need to configure the follow "PeerId": "Vec", "Key": "Vec", "TokenId": "u128", + "RoleKey": "Role", "TokenMetadataKey": "[u8; 32]", "TokenMetadataValue": "MetadataValue", "Token": { "id": "TokenId", - "owner": "AccountId", + "roles": "BTreeMap", "creator": "AccountId", "created_at": "BlockNumber", "destroyed_at": "Option", @@ -120,15 +122,18 @@ In order to use the API within `polkadot.js` you'll need to configure the follow "_enum": { "File": "Hash", "Literal": "[u8; 32]", - "None": "null", - }, + "None": "null" + } }, + "Role": { + "_enum": ["Admin", "ManufacturingEngineer", "ProcurementBuyer", "ProcurementPlanner", "Supplier"] + } } ``` ### SimpleNFT pallet -The `SimpleNFT` pallet exposes an extrinsic for minting/burning tokens and a storage format that allows their retrieval. All of the additional types listed above, apart from `PeerId`, are for the `SimpleNFT` pallet. +The `SimpleNFT` pallet exposes an extrinsic for minting/burning tokens and a storage format that allows their retrieval. Note: The json object with types, described above, has been upgraded from `"Address": "AccountId", "LookupSource": "AccountId"` to `"Address": "MultiAddress", "LookupSource": "MultiAddress"` and it also needs to be used in conjunction with the new version of _PolkaDot JS_, **v4.7.2** or higher. @@ -136,13 +141,13 @@ Two storage endpoints are then exposed under `SimpleNFT` for the id of the last ```rust LastToken get(fn last_token): T::TokenId; -TokensById get(fn tokens_by_id): map T::TokenId => Token; +TokensById get(fn tokens_by_id): map T::TokenId => Token; ``` Tokens can be minted/burnt by calling the following extrinsic under `SimpleNFT`: ```rust -pub fn run_process(origin, inputs: Vec, outputs: Vec<(T::AccountId, BTreeMap)> -> dispatch::DispatchResult { ... } +pub fn run_process(origin, inputs: Vec, outputs: Vec<(BTreeMap, BTreeMap)> -> dispatch::DispatchResult { ... } ``` All of this functionality can be easily accessed using [https://polkadot.js.org/apps](https://polkadot.js.org/apps) against a running `dev` node. You will need to add a network endpoint of `ws://localhost:9944` under `Settings` and apply the above type configurations in the `Settings/Developer` tab. diff --git a/node/Cargo.toml b/node/Cargo.toml index 8b6a2766..ad301fa6 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.3.0' +version = '2.4.0' [[bin]] name = 'vitalam-node' diff --git a/pallets/simple-nft/src/benchmarking.rs b/pallets/simple-nft/src/benchmarking.rs index 421b1218..592a44f8 100644 --- a/pallets/simple-nft/src/benchmarking.rs +++ b/pallets/simple-nft/src/benchmarking.rs @@ -12,13 +12,15 @@ use crate::Module as SimpleNFT; const SEED: u32 = 0; fn add_nfts(r: u32) -> Result<(), &'static str> { - let owner: T::AccountId = account("owner", 0, SEED); + let account_id: T::AccountId = account("owner", 0, SEED); + let mut roles = BTreeMap::new(); let mut metadata = BTreeMap::new(); + roles.insert(T::RoleKey::default(), account_id.clone()); metadata.insert(T::TokenMetadataKey::default(), T::TokenMetadataValue::default()); // let _ = T::Currency::make_free_balance_be(&owner, BalanceOf::::max_value()); - let outputs: Vec<_> = (0..r).map(|_| (owner.clone(), metadata.clone())).collect(); - SimpleNFT::::run_process(RawOrigin::Signed(owner.clone()).into(), Vec::new(), outputs)?; + let outputs: Vec<_> = (0..r).map(|_| (roles.clone(), metadata.clone())).collect(); + SimpleNFT::::run_process(RawOrigin::Signed(account_id.clone()).into(), Vec::new(), outputs)?; let expected_last_token = nth_token_id::(r)?; @@ -38,11 +40,19 @@ fn mk_inputs(i: u32) -> Result, &'static str> { fn mk_outputs( o: u32, -) -> Result)>, &'static str> { - let owner: T::AccountId = account("owner", 0, SEED); +) -> Result< + Vec<( + BTreeMap, + BTreeMap, + )>, + &'static str, +> { + let account_id: T::AccountId = account("owner", 0, SEED); + let mut roles = BTreeMap::new(); let mut metadata = BTreeMap::new(); + roles.insert(T::RoleKey::default(), account_id.clone()); metadata.insert(T::TokenMetadataKey::default(), T::TokenMetadataValue::default()); - let outputs = (0..o).map(|_| (owner.clone(), metadata.clone())).collect::>(); + let outputs = (0..o).map(|_| (roles.clone(), metadata.clone())).collect::>(); Ok(outputs) } diff --git a/pallets/simple-nft/src/lib.rs b/pallets/simple-nft/src/lib.rs index 03974a54..0227ca17 100644 --- a/pallets/simple-nft/src/lib.rs +++ b/pallets/simple-nft/src/lib.rs @@ -20,9 +20,9 @@ mod benchmarking; #[derive(Encode, Decode, Default, Clone, PartialEq)] #[cfg_attr(feature = "std", derive(Debug))] -pub struct Token { +pub struct Token { id: TokenId, - owner: AccountId, + roles: BTreeMap, creator: AccountId, created_at: BlockNumber, destroyed_at: Option, @@ -49,6 +49,8 @@ pub mod pallet { type Event: From> + IsType<::Event>; type TokenId: Parameter + AtLeast32Bit + Default + Copy + Codec; + type RoleKey: Parameter + Default + Ord; + type TokenMetadataKey: Parameter + Default + Ord; type TokenMetadataValue: Parameter + Default; @@ -78,7 +80,7 @@ pub mod pallet { _, Blake2_128Concat, T::TokenId, - Token, + Token, ValueQuery, /*, DefaultForExampleStorage*/ >; @@ -98,8 +100,10 @@ pub mod pallet { NotOwned, /// Mutation was attempted on token that has already been burnt AlreadyBurnt, - /// Mutation was attempted with too many metadata items + /// Token mint was attempted with too many metadata items TooManyMetadataItems, + /// Token mint was attempted without setting a default role + NoDefaultRole, } // The pallet's dispatchable functions. @@ -111,7 +115,10 @@ pub mod pallet { pub(super) fn run_process( origin: OriginFor, inputs: Vec, - outputs: Vec<(T::AccountId, BTreeMap)>, + outputs: Vec<( + BTreeMap, + BTreeMap, + )>, ) -> DispatchResultWithPostInfo { // Check it was signed and get the signer let sender = ensure_signed(origin)?; @@ -124,8 +131,11 @@ pub mod pallet { // INPUT VALIDATION - // check metadata count for output in outputs.iter() { + // check at least a default role has been set + ensure!(output.0.contains_key(&T::RoleKey::default()), Error::::NoDefaultRole); + + // check metadata count ensure!( output.1.len() <= T::MaxMetadataCount::get() as usize, Error::::TooManyMetadataItems @@ -135,7 +145,7 @@ pub mod pallet { // check origin owns inputs and that inputs have not been burnt for id in inputs.iter() { let token = >::get(id); - ensure!(token.owner == sender, Error::::NotOwned); + ensure!(token.roles[&T::RoleKey::default()] == sender, Error::::NotOwned); ensure!(token.children == None, Error::::AlreadyBurnt); } @@ -147,13 +157,13 @@ pub mod pallet { // Create new tokens getting a tuple of the last token created and the complete Vec of tokens created let (last, children) = outputs .iter() - .fold((last, Vec::new()), |(last, children), (owner, metadata)| { + .fold((last, Vec::new()), |(last, children), (roles, metadata)| { let next = _next_token(last); >::insert( next, Token { id: next, - owner: owner.clone(), + roles: roles.clone(), creator: sender.clone(), created_at: now, destroyed_at: None, diff --git a/pallets/simple-nft/src/mock.rs b/pallets/simple-nft/src/mock.rs index 4d79f105..8a5bd5ea 100644 --- a/pallets/simple-nft/src/mock.rs +++ b/pallets/simple-nft/src/mock.rs @@ -65,6 +65,18 @@ parameter_types! { pub const MaxMetadataCount: u32 = 3; } +#[derive(Encode, Decode, Clone, PartialEq, Debug, Eq, Ord, PartialOrd)] +pub enum Role { + Admin, + NotAdmin, +} + +impl Default for Role { + fn default() -> Self { + Role::Admin + } +} + #[derive(Encode, Decode, Clone, PartialEq, Debug, Eq)] pub enum MetadataValue { File(Hash), @@ -82,6 +94,7 @@ impl pallet_simple_nft::Config for Test { type Event = Event; type TokenId = u64; + type RoleKey = Role; type TokenMetadataKey = u64; type TokenMetadataValue = MetadataValue; diff --git a/pallets/simple-nft/src/tests.rs b/pallets/simple-nft/src/tests.rs index f68d8b37..5ce0e113 100644 --- a/pallets/simple-nft/src/tests.rs +++ b/pallets/simple-nft/src/tests.rs @@ -11,11 +11,12 @@ use sp_std::iter::FromIterator; fn it_works_for_creating_token_with_file() { new_test_ext().execute_with(|| { // create a token with no parents + let roles = BTreeMap::from_iter(vec![(Default::default(), 1)]); let metadata = BTreeMap::from_iter(vec![(0, MetadataValue::File(H256::zero()))]); assert_ok!(SimpleNFTModule::run_process( Origin::signed(1), Vec::new(), - vec![(1, metadata.clone())] + vec![(roles.clone(), metadata.clone())] )); // last token should be 1 assert_eq!(SimpleNFTModule::last_token(), 1); @@ -25,7 +26,7 @@ fn it_works_for_creating_token_with_file() { token, Token { id: 1, - owner: 1, + roles: roles.clone(), creator: 1, created_at: 0, destroyed_at: None, @@ -41,11 +42,12 @@ fn it_works_for_creating_token_with_file() { fn it_works_for_creating_token_with_literal() { new_test_ext().execute_with(|| { // create a token with no parents + let roles = BTreeMap::from_iter(vec![(Default::default(), 1)]); let metadata = BTreeMap::from_iter(vec![(0, MetadataValue::Literal([0]))]); assert_ok!(SimpleNFTModule::run_process( Origin::signed(1), Vec::new(), - vec![(1, metadata.clone())] + vec![(roles.clone(), metadata.clone())] )); // last token should be 1 assert_eq!(SimpleNFTModule::last_token(), 1); @@ -55,7 +57,7 @@ fn it_works_for_creating_token_with_literal() { token, Token { id: 1, - owner: 1, + roles: roles.clone(), creator: 1, created_at: 0, destroyed_at: None, @@ -71,11 +73,12 @@ fn it_works_for_creating_token_with_literal() { fn it_works_for_creating_token_with_no_metadata_value() { new_test_ext().execute_with(|| { // create a token with no parents + let roles = BTreeMap::from_iter(vec![(Default::default(), 1)]); let metadata = BTreeMap::from_iter(vec![(0, MetadataValue::None)]); assert_ok!(SimpleNFTModule::run_process( Origin::signed(1), Vec::new(), - vec![(1, metadata.clone())] + vec![(roles.clone(), metadata.clone())] )); // last token should be 1 assert_eq!(SimpleNFTModule::last_token(), 1); @@ -85,7 +88,7 @@ fn it_works_for_creating_token_with_no_metadata_value() { token, Token { id: 1, - owner: 1, + roles: roles.clone(), creator: 1, created_at: 0, destroyed_at: None, @@ -101,6 +104,7 @@ fn it_works_for_creating_token_with_no_metadata_value() { fn it_works_for_creating_token_with_multiple_metadata_items() { new_test_ext().execute_with(|| { // create a token with no parents + let roles = BTreeMap::from_iter(vec![(Default::default(), 1)]); let metadata = BTreeMap::from_iter(vec![ (0, MetadataValue::File(H256::zero())), (1, MetadataValue::Literal([0])), @@ -109,7 +113,7 @@ fn it_works_for_creating_token_with_multiple_metadata_items() { assert_ok!(SimpleNFTModule::run_process( Origin::signed(1), Vec::new(), - vec![(1, metadata.clone())] + vec![(roles.clone(), metadata.clone())] )); // last token should be 1 assert_eq!(SimpleNFTModule::last_token(), 1); @@ -119,7 +123,38 @@ fn it_works_for_creating_token_with_multiple_metadata_items() { token, Token { id: 1, - owner: 1, + roles: roles.clone(), + creator: 1, + created_at: 0, + destroyed_at: None, + metadata: metadata.clone(), + parents: Vec::new(), + children: None + } + ); + }); +} + +#[test] +fn it_works_for_creating_token_with_multiple_roles() { + new_test_ext().execute_with(|| { + // create a token with no parents + let roles = BTreeMap::from_iter(vec![(Default::default(), 1), (Role::NotAdmin, 2)]); + let metadata = BTreeMap::from_iter(vec![(0, MetadataValue::None)]); + assert_ok!(SimpleNFTModule::run_process( + Origin::signed(1), + Vec::new(), + vec![(roles.clone(), metadata.clone())] + )); + // last token should be 1 + assert_eq!(SimpleNFTModule::last_token(), 1); + // get the token + let token = SimpleNFTModule::tokens_by_id(1); + assert_eq!( + token, + Token { + id: 1, + roles: roles.clone(), creator: 1, created_at: 0, destroyed_at: None, @@ -135,13 +170,18 @@ fn it_works_for_creating_token_with_multiple_metadata_items() { fn it_works_for_creating_many_token() { new_test_ext().execute_with(|| { // create a token with no parents + let roles = BTreeMap::from_iter(vec![(Default::default(), 1)]); let metadata0 = BTreeMap::from_iter(vec![(0, MetadataValue::File(H256::zero()))]); let metadata1 = BTreeMap::from_iter(vec![(0, MetadataValue::File(H256::zero()))]); let metadata2 = BTreeMap::from_iter(vec![(0, MetadataValue::File(H256::zero()))]); assert_ok!(SimpleNFTModule::run_process( Origin::signed(1), Vec::new(), - vec![(1, metadata0.clone()), (1, metadata1.clone()), (1, metadata2.clone())] + vec![ + (roles.clone(), metadata0.clone()), + (roles.clone(), metadata1.clone()), + (roles.clone(), metadata2.clone()) + ] )); // last token should be 3 assert_eq!(SimpleNFTModule::last_token(), 3); @@ -151,7 +191,7 @@ fn it_works_for_creating_many_token() { token, Token { id: 1, - owner: 1, + roles: roles.clone(), creator: 1, created_at: 0, destroyed_at: None, @@ -165,7 +205,7 @@ fn it_works_for_creating_many_token() { token, Token { id: 2, - owner: 1, + roles: roles.clone(), creator: 1, created_at: 0, destroyed_at: None, @@ -179,7 +219,7 @@ fn it_works_for_creating_many_token() { token, Token { id: 3, - owner: 1, + roles: roles.clone(), creator: 1, created_at: 0, destroyed_at: None, @@ -195,13 +235,18 @@ fn it_works_for_creating_many_token() { fn it_works_for_creating_many_token_with_varied_metadata() { new_test_ext().execute_with(|| { // create a token with no parents + let roles = BTreeMap::from_iter(vec![(Default::default(), 1)]); let metadata0 = BTreeMap::from_iter(vec![(0, MetadataValue::None), (1, MetadataValue::File(H256::zero()))]); let metadata1 = BTreeMap::from_iter(vec![(0, MetadataValue::Literal([0]))]); let metadata2 = BTreeMap::from_iter(vec![(1, MetadataValue::Literal([0]))]); assert_ok!(SimpleNFTModule::run_process( Origin::signed(1), Vec::new(), - vec![(1, metadata0.clone()), (1, metadata1.clone()), (1, metadata2.clone())] + vec![ + (roles.clone(), metadata0.clone()), + (roles.clone(), metadata1.clone()), + (roles.clone(), metadata2.clone()) + ] )); // last token should be 3 assert_eq!(SimpleNFTModule::last_token(), 3); @@ -211,7 +256,7 @@ fn it_works_for_creating_many_token_with_varied_metadata() { token, Token { id: 1, - owner: 1, + roles: roles.clone(), creator: 1, created_at: 0, destroyed_at: None, @@ -225,7 +270,7 @@ fn it_works_for_creating_many_token_with_varied_metadata() { token, Token { id: 2, - owner: 1, + roles: roles.clone(), creator: 1, created_at: 0, destroyed_at: None, @@ -239,7 +284,7 @@ fn it_works_for_creating_many_token_with_varied_metadata() { token, Token { id: 3, - owner: 1, + roles: roles.clone(), creator: 1, created_at: 0, destroyed_at: None, @@ -254,8 +299,9 @@ fn it_works_for_creating_many_token_with_varied_metadata() { #[test] fn it_works_for_destroying_single_token() { new_test_ext().execute_with(|| { + let roles = BTreeMap::from_iter(vec![(Default::default(), 1)]); let metadata = BTreeMap::from_iter(vec![(0, MetadataValue::None)]); - SimpleNFTModule::run_process(Origin::signed(1), Vec::new(), vec![(1, metadata.clone())]).unwrap(); + SimpleNFTModule::run_process(Origin::signed(1), Vec::new(), vec![(roles.clone(), metadata.clone())]).unwrap(); // create a token with no parents assert_ok!(SimpleNFTModule::run_process(Origin::signed(1), vec![1], Vec::new())); // assert no more tokens were created @@ -266,7 +312,7 @@ fn it_works_for_destroying_single_token() { token, Token { id: 1, - owner: 1, + roles: roles.clone(), creator: 1, created_at: 0, destroyed_at: Some(0), @@ -281,13 +327,18 @@ fn it_works_for_destroying_single_token() { #[test] fn it_works_for_destroying_many_tokens() { new_test_ext().execute_with(|| { + let roles = BTreeMap::from_iter(vec![(Default::default(), 1)]); let metadata0 = BTreeMap::from_iter(vec![(0, MetadataValue::None)]); let metadata1 = BTreeMap::from_iter(vec![(0, MetadataValue::None)]); let metadata2 = BTreeMap::from_iter(vec![(0, MetadataValue::None)]); SimpleNFTModule::run_process( Origin::signed(1), Vec::new(), - vec![(1, metadata0.clone()), (1, metadata1.clone()), (1, metadata2.clone())], + vec![ + (roles.clone(), metadata0.clone()), + (roles.clone(), metadata1.clone()), + (roles.clone(), metadata2.clone()), + ], ) .unwrap(); // create a token with no parents @@ -304,7 +355,7 @@ fn it_works_for_destroying_many_tokens() { token, Token { id: 1, - owner: 1, + roles: roles.clone(), creator: 1, created_at: 0, destroyed_at: Some(0), @@ -318,7 +369,7 @@ fn it_works_for_destroying_many_tokens() { token, Token { id: 2, - owner: 1, + roles: roles.clone(), creator: 1, created_at: 0, destroyed_at: Some(0), @@ -332,7 +383,7 @@ fn it_works_for_destroying_many_tokens() { token, Token { id: 3, - owner: 1, + roles: roles.clone(), creator: 1, created_at: 0, destroyed_at: Some(0), @@ -347,14 +398,16 @@ fn it_works_for_destroying_many_tokens() { #[test] fn it_works_for_creating_and_destroy_single_tokens() { new_test_ext().execute_with(|| { + let roles1 = BTreeMap::from_iter(vec![(Default::default(), 1)]); + let roles2 = BTreeMap::from_iter(vec![(Default::default(), 2)]); let metadata0 = BTreeMap::from_iter(vec![(0, MetadataValue::None)]); let metadata1 = BTreeMap::from_iter(vec![(0, MetadataValue::None)]); - SimpleNFTModule::run_process(Origin::signed(1), Vec::new(), vec![(1, metadata0.clone())]).unwrap(); + SimpleNFTModule::run_process(Origin::signed(1), Vec::new(), vec![(roles1.clone(), metadata0.clone())]).unwrap(); // create a token with a parent assert_ok!(SimpleNFTModule::run_process( Origin::signed(1), vec![1], - vec![(2, metadata1.clone())] + vec![(roles2.clone(), metadata1.clone())] )); // assert 1 more token was created assert_eq!(SimpleNFTModule::last_token(), 2); @@ -364,7 +417,7 @@ fn it_works_for_creating_and_destroy_single_tokens() { token, Token { id: 1, - owner: 1, + roles: roles1.clone(), creator: 1, created_at: 0, destroyed_at: Some(0), @@ -378,7 +431,7 @@ fn it_works_for_creating_and_destroy_single_tokens() { token, Token { id: 2, - owner: 2, + roles: roles2.clone(), creator: 1, created_at: 0, destroyed_at: None, @@ -393,6 +446,8 @@ fn it_works_for_creating_and_destroy_single_tokens() { #[test] fn it_works_for_creating_and_destroy_many_tokens() { new_test_ext().execute_with(|| { + let roles1 = BTreeMap::from_iter(vec![(Default::default(), 1)]); + let roles2 = BTreeMap::from_iter(vec![(Default::default(), 2)]); let metadata0 = BTreeMap::from_iter(vec![(0, MetadataValue::None)]); let metadata1 = BTreeMap::from_iter(vec![(0, MetadataValue::None)]); let metadata2 = BTreeMap::from_iter(vec![(0, MetadataValue::None)]); @@ -400,14 +455,14 @@ fn it_works_for_creating_and_destroy_many_tokens() { SimpleNFTModule::run_process( Origin::signed(1), Vec::new(), - vec![(1, metadata0.clone()), (1, metadata1.clone())], + vec![(roles1.clone(), metadata0.clone()), (roles1.clone(), metadata1.clone())], ) .unwrap(); // create a token with 2 parents assert_ok!(SimpleNFTModule::run_process( Origin::signed(1), vec![1, 2], - vec![(1, metadata2.clone()), (2, metadata3.clone())] + vec![(roles1.clone(), metadata2.clone()), (roles2.clone(), metadata3.clone())] )); // assert 2 more tokens were created assert_eq!(SimpleNFTModule::last_token(), 4); @@ -417,7 +472,7 @@ fn it_works_for_creating_and_destroy_many_tokens() { token, Token { id: 1, - owner: 1, + roles: roles1.clone(), creator: 1, created_at: 0, destroyed_at: Some(0), @@ -431,7 +486,7 @@ fn it_works_for_creating_and_destroy_many_tokens() { token, Token { id: 2, - owner: 1, + roles: roles1.clone(), creator: 1, created_at: 0, destroyed_at: Some(0), @@ -446,7 +501,7 @@ fn it_works_for_creating_and_destroy_many_tokens() { token, Token { id: 3, - owner: 1, + roles: roles1.clone(), creator: 1, created_at: 0, destroyed_at: None, @@ -460,7 +515,7 @@ fn it_works_for_creating_and_destroy_many_tokens() { token, Token { id: 4, - owner: 2, + roles: roles2.clone(), creator: 1, created_at: 0, destroyed_at: None, @@ -472,11 +527,32 @@ fn it_works_for_creating_and_destroy_many_tokens() { }); } +#[test] +fn it_fails_for_destroying_single_token_as_incorrect_role() { + new_test_ext().execute_with(|| { + let roles = BTreeMap::from_iter(vec![(Default::default(), 1), (Role::NotAdmin, 2)]); + let metadata = BTreeMap::from_iter(vec![(0, MetadataValue::None)]); + SimpleNFTModule::run_process(Origin::signed(1), Vec::new(), vec![(roles.clone(), metadata.clone())]).unwrap(); + // get old token + let token = SimpleNFTModule::tokens_by_id(1); + // Try to destroy token as incorrect user + assert_err!( + SimpleNFTModule::run_process(Origin::signed(2), vec![1], Vec::new()), + Error::::NotOwned + ); + // assert no more tokens were created + assert_eq!(SimpleNFTModule::last_token(), 1); + // assert old token hasn't changed + assert_eq!(token, SimpleNFTModule::tokens_by_id(1)); + }); +} + #[test] fn it_fails_for_destroying_single_token_as_other_signer() { new_test_ext().execute_with(|| { + let roles = BTreeMap::from_iter(vec![(Default::default(), 1)]); let metadata = BTreeMap::from_iter(vec![(0, MetadataValue::None)]); - SimpleNFTModule::run_process(Origin::signed(1), Vec::new(), vec![(1, metadata.clone())]).unwrap(); + SimpleNFTModule::run_process(Origin::signed(1), Vec::new(), vec![(roles.clone(), metadata.clone())]).unwrap(); // get old token let token = SimpleNFTModule::tokens_by_id(1); // Try to destroy token as incorrect user @@ -494,10 +570,11 @@ fn it_fails_for_destroying_single_token_as_other_signer() { #[test] fn it_fails_for_destroying_multiple_tokens_as_other_signer() { new_test_ext().execute_with(|| { + let roles = BTreeMap::from_iter(vec![(Default::default(), 1)]); let metadata0 = BTreeMap::from_iter(vec![(0, MetadataValue::None)]); let metadata1 = BTreeMap::from_iter(vec![(0, MetadataValue::None)]); - SimpleNFTModule::run_process(Origin::signed(2), Vec::new(), vec![(1, metadata0.clone())]).unwrap(); - SimpleNFTModule::run_process(Origin::signed(1), Vec::new(), vec![(1, metadata1.clone())]).unwrap(); + SimpleNFTModule::run_process(Origin::signed(2), Vec::new(), vec![(roles.clone(), metadata0.clone())]).unwrap(); + SimpleNFTModule::run_process(Origin::signed(1), Vec::new(), vec![(roles.clone(), metadata1.clone())]).unwrap(); // get old token let token_1 = SimpleNFTModule::tokens_by_id(1); let token_2 = SimpleNFTModule::tokens_by_id(2); @@ -518,15 +595,16 @@ fn it_fails_for_destroying_multiple_tokens_as_other_signer() { #[test] fn it_fails_for_destroying_single_burnt_token() { new_test_ext().execute_with(|| { + let roles = BTreeMap::from_iter(vec![(Default::default(), 1)]); let metadata0 = BTreeMap::from_iter(vec![(0, MetadataValue::None)]); let metadata1 = BTreeMap::from_iter(vec![(0, MetadataValue::None)]); - SimpleNFTModule::run_process(Origin::signed(1), Vec::new(), vec![(1, metadata0.clone())]).unwrap(); + SimpleNFTModule::run_process(Origin::signed(1), Vec::new(), vec![(roles.clone(), metadata0.clone())]).unwrap(); SimpleNFTModule::run_process(Origin::signed(1), vec![1], Vec::new()).unwrap(); // get old token let token = SimpleNFTModule::tokens_by_id(1); // Try to destroy token as incorrect user assert_err!( - SimpleNFTModule::run_process(Origin::signed(1), vec![1], vec![(1, metadata1.clone())]), + SimpleNFTModule::run_process(Origin::signed(1), vec![1], vec![(roles.clone(), metadata1.clone())]), Error::::AlreadyBurnt ); // assert no more tokens were created @@ -539,13 +617,14 @@ fn it_fails_for_destroying_single_burnt_token() { #[test] fn it_fails_for_destroying_multiple_tokens_with_burnt_token() { new_test_ext().execute_with(|| { + let roles = BTreeMap::from_iter(vec![(Default::default(), 1)]); let metadata0 = BTreeMap::from_iter(vec![(0, MetadataValue::None)]); let metadata1 = BTreeMap::from_iter(vec![(0, MetadataValue::None)]); let metadata2 = BTreeMap::from_iter(vec![(0, MetadataValue::None)]); SimpleNFTModule::run_process( Origin::signed(1), Vec::new(), - vec![(1, metadata0.clone()), (1, metadata1.clone())], + vec![(roles.clone(), metadata0.clone()), (roles.clone(), metadata1.clone())], ) .unwrap(); SimpleNFTModule::run_process(Origin::signed(1), vec![1], Vec::new()).unwrap(); @@ -555,7 +634,7 @@ fn it_fails_for_destroying_multiple_tokens_with_burnt_token() { let token_2 = SimpleNFTModule::tokens_by_id(2); // Try to destroy token as incorrect user assert_err!( - SimpleNFTModule::run_process(Origin::signed(1), vec![1, 2], vec![(1, metadata2.clone())]), + SimpleNFTModule::run_process(Origin::signed(1), vec![1, 2], vec![(roles.clone(), metadata2.clone())]), Error::::AlreadyBurnt ); // assert no more tokens were created @@ -570,6 +649,7 @@ fn it_fails_for_destroying_multiple_tokens_with_burnt_token() { #[test] fn it_fails_for_creating_single_token_with_too_many_metadata_items() { new_test_ext().execute_with(|| { + let roles = BTreeMap::from_iter(vec![(Default::default(), 1)]); let metadata0 = BTreeMap::from_iter(vec![(0, MetadataValue::None)]); let metadata_too_many = BTreeMap::from_iter(vec![ (0, MetadataValue::None), @@ -577,12 +657,16 @@ fn it_fails_for_creating_single_token_with_too_many_metadata_items() { (2, MetadataValue::None), (3, MetadataValue::None), ]); - SimpleNFTModule::run_process(Origin::signed(1), Vec::new(), vec![(1, metadata0.clone())]).unwrap(); + SimpleNFTModule::run_process(Origin::signed(1), Vec::new(), vec![(roles.clone(), metadata0.clone())]).unwrap(); // get old token let token = SimpleNFTModule::tokens_by_id(1); // Try to create token with too many metadata items assert_err!( - SimpleNFTModule::run_process(Origin::signed(1), Vec::new(), vec![(1, metadata_too_many.clone())]), + SimpleNFTModule::run_process( + Origin::signed(1), + Vec::new(), + vec![(roles.clone(), metadata_too_many.clone())] + ), Error::::TooManyMetadataItems ); // assert no more tokens were created @@ -591,3 +675,28 @@ fn it_fails_for_creating_single_token_with_too_many_metadata_items() { assert_eq!(token, SimpleNFTModule::tokens_by_id(1)); }); } + +#[test] +fn it_fails_for_creating_single_token_with_no_default_role() { + new_test_ext().execute_with(|| { + let roles = BTreeMap::from_iter(vec![(Default::default(), 1)]); + let roles_empty = BTreeMap::new(); + let metadata = BTreeMap::from_iter(vec![(0, MetadataValue::None)]); + SimpleNFTModule::run_process(Origin::signed(1), Vec::new(), vec![(roles.clone(), metadata.clone())]).unwrap(); + // get old token + let token = SimpleNFTModule::tokens_by_id(1); + // Try to create token without setting default role in roles + assert_err!( + SimpleNFTModule::run_process( + Origin::signed(1), + Vec::new(), + vec![(roles_empty.clone(), metadata.clone())] + ), + Error::::NoDefaultRole + ); + // assert no more tokens were created + assert_eq!(SimpleNFTModule::last_token(), 1); + // assert old token hasn't changed + assert_eq!(token, SimpleNFTModule::tokens_by_id(1)); + }); +} diff --git a/pallets/simple-nft/src/weights.rs b/pallets/simple-nft/src/weights.rs index ebd0d3dc..3b28fd14 100644 --- a/pallets/simple-nft/src/weights.rs +++ b/pallets/simple-nft/src/weights.rs @@ -28,14 +28,16 @@ pub trait WeightInfo { pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { fn run_process(i: usize, o: usize) -> Weight { - (104_267_000 as Weight) - // Standard Error: 6_647_000 - .saturating_add((13_768_000 as Weight).saturating_mul(i as Weight)) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(o as Weight))) + (14_049_000 as Weight) + // Standard Error: 7_000 + .saturating_add((12_902_000 as Weight).saturating_mul(i as Weight)) + // Standard Error: 7_000 + .saturating_add((5_897_000 as Weight).saturating_mul(o as Weight)) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) + .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(o as Weight))) } } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index bffc45af..438b9c1b 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -291,6 +291,21 @@ parameter_types! { pub const MaxMetadataCount: u32 = 16; } +#[derive(Encode, Decode, Clone, PartialEq, Debug, Eq, Ord, PartialOrd)] +pub enum Role { + Admin = 0, + ManufacturingEngineer = 1, + ProcurementBuyer = 2, + ProcurementPlanner = 3, + Supplier = 4, +} + +impl Default for Role { + fn default() -> Self { + Role::Admin + } +} + #[derive(Encode, Decode, Clone, PartialEq, Debug, Eq)] pub enum MetadataValue { File(Hash), @@ -308,6 +323,7 @@ impl Default for MetadataValue { impl pallet_simple_nft::Config for Runtime { type Event = Event; type TokenId = u128; + type RoleKey = Role; type TokenMetadataKey = [u8; 32]; type TokenMetadataValue = MetadataValue; type WeightInfo = pallet_simple_nft::weights::SubstrateWeight;