diff --git a/pallets/dispenser/src/benchmarking.rs b/pallets/dispenser/src/benchmarking.rs index 479de8a97..9e998ab95 100644 --- a/pallets/dispenser/src/benchmarking.rs +++ b/pallets/dispenser/src/benchmarking.rs @@ -23,7 +23,7 @@ use crate::Pallet as Dispenser; use frame_benchmarking::v2::*; use frame_support::traits::{EnsureOrigin, Get}; use frame_system::RawOrigin; -use polimec_common_test_utils::{generate_did_from_account, get_mock_jwt}; +use polimec_common_test_utils::{generate_did_from_account, get_mock_jwt_with_cid}; use sp_runtime::traits::One; fn assert_last_event(generic_event: ::RuntimeEvent) { @@ -40,7 +40,7 @@ mod benchmarks { assert_eq!(Dispensed::::get(did.clone()), None); CurrencyOf::::deposit_creating(&Dispenser::::dispense_account(), T::InitialDispenseAmount::get()); - let jwt = get_mock_jwt(caller.clone(), InvestorType::Retail, did.clone()); + let jwt = get_mock_jwt_with_cid(caller.clone(), InvestorType::Retail, did.clone(), T::WhitelistedPolicy::get()); #[extrinsic_call] dispense(RawOrigin::Signed(caller.clone()), jwt); diff --git a/pallets/dispenser/src/lib.rs b/pallets/dispenser/src/lib.rs index 5af6f140a..5b3982a02 100644 --- a/pallets/dispenser/src/lib.rs +++ b/pallets/dispenser/src/lib.rs @@ -107,6 +107,10 @@ pub mod pallet { /// A type representing the weights required by the dispatchables of this pallet. type WeightInfo: crate::weights::WeightInfo; + + /// The Whitelisted policy for the dispenser. Users' credentials should have the same + /// policy to be eligible for token dispensing. + type WhitelistedPolicy: Get; } #[pallet::pallet] @@ -133,6 +137,8 @@ pub mod pallet { DispenserDepleted, /// The dispense amount is too low. It must be greater than the free dispense amount. DispenseAmountTooLow, + /// The origin does not have the required credentials. + InvalidCredential, } #[pallet::call] @@ -147,9 +153,10 @@ pub mod pallet { #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::dispense())] pub fn dispense(origin: OriginFor, jwt: UntrustedToken) -> DispatchResultWithPostInfo { - let (who, did, _investor_type, _) = + let (who, did, _investor_type, whitelisted_policy) = T::InvestorOrigin::ensure_origin(origin, &jwt, T::VerifierPublicKey::get())?; ensure!(Dispensed::::get(&did).is_none(), Error::::DispensedAlreadyToDid); + ensure!(whitelisted_policy == T::WhitelistedPolicy::get(), Error::::InvalidCredential); let amount = DispenseAmount::::get(); ensure!(CurrencyOf::::free_balance(&Self::dispense_account()) >= amount, Error::::DispenserDepleted); diff --git a/pallets/dispenser/src/mock.rs b/pallets/dispenser/src/mock.rs index 9bf653cc0..dc5f9c694 100644 --- a/pallets/dispenser/src/mock.rs +++ b/pallets/dispenser/src/mock.rs @@ -19,7 +19,8 @@ use frame_support::{derive_impl, ord_parameter_types, parameter_types, traits::tokens::WithdrawReasons, PalletId}; use frame_system as system; use frame_system::EnsureSignedBy; -use polimec_common::credentials::EnsureInvestor; +use polimec_common::credentials::{Cid, EnsureInvestor}; +use polimec_common_test_utils::generate_cid_from_string; use sp_runtime::{traits::ConvertInto, BuildStorage}; type Block = frame_system::mocking::MockBlock; @@ -69,6 +70,7 @@ impl pallet_vesting::Config for Test { const MAX_VESTING_SCHEDULES: u32 = 28; } +const IPFS_CID: &str = "QmeuJ24ffwLAZppQcgcggJs3n689bewednYkuc8Bx5Gngz"; parameter_types! { pub const InitialDispenseAmount: u64 = 100; pub const FreeDispenseAmount: u64 = 5; @@ -79,6 +81,7 @@ parameter_types! { 32, 118, 30, 171, 58, 212, 197, 27, 146, 122, 255, 243, 34, 245, 90, 244, 221, 37, 253, 195, 18, 202, 111, 55, 39, 48, 123, 17, 101, 78, 215, 94, ]; + pub WhitelistedPolicy: Cid = generate_cid_from_string(IPFS_CID); } ord_parameter_types! { @@ -98,6 +101,7 @@ impl crate::Config for Test { type VestPeriod = VestPeriod; type VestingSchedule = Vesting; type WeightInfo = (); + type WhitelistedPolicy = WhitelistedPolicy; } pub(crate) struct ExtBuilder { diff --git a/pallets/dispenser/src/tests.rs b/pallets/dispenser/src/tests.rs index 56e4b05e6..68bd7de83 100644 --- a/pallets/dispenser/src/tests.rs +++ b/pallets/dispenser/src/tests.rs @@ -21,7 +21,7 @@ use crate as pallet_dispenser; use crate::mock::*; use frame_support::{assert_noop, assert_ok}; use polimec_common::credentials::InvestorType; -use polimec_common_test_utils::{generate_did_from_account, get_mock_jwt}; +use polimec_common_test_utils::{generate_cid_from_string, generate_did_from_account, get_mock_jwt_with_cid}; use sp_runtime::DispatchError; mod admin { @@ -66,7 +66,8 @@ mod dispense { // User has no balance. assert_eq!(Balances::free_balance(1), 0); // User can dispense tokens for free. - let jwt = get_mock_jwt(1, InvestorType::Retail, generate_did_from_account(1)); + let jwt = + get_mock_jwt_with_cid(1, InvestorType::Retail, generate_did_from_account(1), WhitelistedPolicy::get()); assert_ok!(Dispenser::dispense(RuntimeOrigin::signed(1), jwt)); // Tokens are dispensed and locked. @@ -85,7 +86,8 @@ mod dispense { #[test] fn user_cannot_dispense_twice() { ExtBuilder::default().build().execute_with(|| { - let jwt = get_mock_jwt(1, InvestorType::Retail, generate_did_from_account(1)); + let jwt = + get_mock_jwt_with_cid(1, InvestorType::Retail, generate_did_from_account(1), WhitelistedPolicy::get()); assert_ok!(Dispenser::dispense(RuntimeOrigin::signed(1), jwt.clone())); assert_noop!(Dispenser::dispense(RuntimeOrigin::signed(1), jwt), Error::::DispensedAlreadyToDid); }); @@ -94,7 +96,8 @@ mod dispense { #[test] fn correct_amount_received_after_dispense_amount_changed() { ExtBuilder::default().dispense_account(2).build().execute_with(|| { - let jwt = get_mock_jwt(1, InvestorType::Retail, generate_did_from_account(1)); + let jwt = + get_mock_jwt_with_cid(1, InvestorType::Retail, generate_did_from_account(1), WhitelistedPolicy::get()); assert_ok!(Dispenser::dispense(RuntimeOrigin::signed(1), jwt)); assert_eq!(Balances::free_balance(1), ::InitialDispenseAmount::get()); assert_eq!(Balances::usable_balance(1), ::FreeDispenseAmount::get()); @@ -109,7 +112,8 @@ mod dispense { // Change the dispense amount. let new_amount: BalanceOf = 50u32.into(); assert_ok!(Dispenser::set_dispense_amount(RuntimeOrigin::signed(Admin::get()), new_amount)); - let jwt = get_mock_jwt(2, InvestorType::Retail, generate_did_from_account(2)); + let jwt = + get_mock_jwt_with_cid(2, InvestorType::Retail, generate_did_from_account(2), WhitelistedPolicy::get()); assert_ok!(Dispenser::dispense(RuntimeOrigin::signed(2), jwt)); assert_eq!(Balances::free_balance(2), new_amount); assert_eq!(Balances::usable_balance(2), ::FreeDispenseAmount::get()); @@ -129,7 +133,12 @@ mod dispense { x * ::InitialDispenseAmount::get() ); for i in 1..=x { - let jwt = get_mock_jwt(i, InvestorType::Retail, generate_did_from_account(i)); + let jwt = get_mock_jwt_with_cid( + i, + InvestorType::Retail, + generate_did_from_account(i), + WhitelistedPolicy::get(), + ); assert_ok!(Dispenser::dispense(RuntimeOrigin::signed(i), jwt)); assert_eq!(Balances::free_balance(i), ::InitialDispenseAmount::get()); assert_eq!(Balances::usable_balance(i), ::FreeDispenseAmount::get()); @@ -145,10 +154,24 @@ mod dispense { assert_noop!( Dispenser::dispense( RuntimeOrigin::signed(x + 1), - get_mock_jwt(x + 1, InvestorType::Retail, generate_did_from_account(x + 1)) + get_mock_jwt_with_cid( + x + 1, + InvestorType::Retail, + generate_did_from_account(x + 1), + WhitelistedPolicy::get() + ) ), Error::::DispenserDepleted ); }); } + + #[test] + fn user_cannot_dispense_with_wrong_policy() { + ExtBuilder::default().build().execute_with(|| { + let wrong_cid = generate_cid_from_string("1111111111111111111111111111111111111111111111111111"); + let jwt = get_mock_jwt_with_cid(1, InvestorType::Retail, generate_did_from_account(1), wrong_cid); + assert_noop!(Dispenser::dispense(RuntimeOrigin::signed(1), jwt.clone()), Error::::InvalidCredential); + }); + } } diff --git a/polimec-common/test-utils/src/lib.rs b/polimec-common/test-utils/src/lib.rs index 8e6fe7414..a18ab6fb3 100644 --- a/polimec-common/test-utils/src/lib.rs +++ b/polimec-common/test-utils/src/lib.rs @@ -130,6 +130,10 @@ pub fn generate_did_from_account(account_id: impl Parameter) -> Did { hex_account.into_bytes().try_into().unwrap() } +pub fn generate_cid_from_string(cid: &str) -> BoundedVec> { + BoundedVec::try_from(cid.as_bytes().to_vec()).unwrap() +} + #[cfg(feature = "std")] pub fn do_request(url: &str) -> String { reqwest::blocking::Client::builder() diff --git a/runtimes/polimec/src/lib.rs b/runtimes/polimec/src/lib.rs index cf1adc875..0c37ee098 100644 --- a/runtimes/polimec/src/lib.rs +++ b/runtimes/polimec/src/lib.rs @@ -1125,6 +1125,7 @@ impl pallet_dispenser::Config for Runtime { type VestPeriod = DispenserVestPeriod; type VestingSchedule = Vesting; type WeightInfo = weights::pallet_dispenser::WeightInfo; + type WhitelistedPolicy = DispenserWhitelistedPolicy; } // Create the runtime by composing the FRAME pallets that were previously configured. diff --git a/runtimes/politest/src/lib.rs b/runtimes/politest/src/lib.rs index cd47a8a17..817ff4659 100644 --- a/runtimes/politest/src/lib.rs +++ b/runtimes/politest/src/lib.rs @@ -1107,6 +1107,7 @@ impl pallet_dispenser::Config for Runtime { type VestPeriod = DispenserVestPeriod; type VestingSchedule = Vesting; type WeightInfo = (); + type WhitelistedPolicy = DispenserWhitelistedPolicy; } // Create the runtime by composing the FRAME pallets that were previously configured. diff --git a/runtimes/shared-configuration/src/assets.rs b/runtimes/shared-configuration/src/assets.rs index 01bf81f1d..0b3241d43 100644 --- a/runtimes/shared-configuration/src/assets.rs +++ b/runtimes/shared-configuration/src/assets.rs @@ -23,6 +23,7 @@ use frame_support::{parameter_types, PalletId}; use orml_traits::DataProvider; use pallet_funding::traits::ProvideAssetPrice; use parachains_common::DAYS; +use polimec_common::credentials::Cid; use sp_arithmetic::FixedPointNumber; parameter_types! { @@ -58,5 +59,5 @@ parameter_types! { pub const DispenserId: PalletId = PalletId(*b"plmc/fct"); pub const DispenserLockPeriod: u32 = DAYS * 365 * 2; // 2 years pub const DispenserVestPeriod: u32 = DAYS * 365 * 2; // 2 years - + pub DispenserWhitelistedPolicy: Cid = (*b"QmVdGSxuWcamYEmYJjR3gvZucqQpp4Jnf6tqJABHwKZVo3").to_vec().try_into().unwrap(); }