From f10246d78e89722f1318786674dafbabb1bbeb79 Mon Sep 17 00:00:00 2001 From: abdulhakim2902 Date: Mon, 19 Sep 2022 12:45:09 +0700 Subject: [PATCH 1/2] fix: improved code readability --- pallets/service-request/src/functions.rs | 59 ++ .../src/impl_service_request.rs | 353 +++++++++ pallets/service-request/src/interface.rs | 13 - pallets/service-request/src/lib.rs | 704 +----------------- pallets/service-request/src/types.rs | 150 ++++ 5 files changed, 573 insertions(+), 706 deletions(-) create mode 100644 pallets/service-request/src/functions.rs create mode 100644 pallets/service-request/src/impl_service_request.rs create mode 100644 pallets/service-request/src/types.rs diff --git a/pallets/service-request/src/functions.rs b/pallets/service-request/src/functions.rs new file mode 100644 index 00000000..dcdd301f --- /dev/null +++ b/pallets/service-request/src/functions.rs @@ -0,0 +1,59 @@ +use crate::*; + +use frame_support::{ + dispatch::DispatchError, sp_runtime::traits::AccountIdConversion, traits::ExistenceRequirement, + PalletId, +}; + +pub const PALLET_ID: PalletId = PalletId(*b"reqsrvc!"); + +impl Pallet { + /// Pallet Methods + pub fn staking_account_id(request_id: ServiceIdOf) -> AccountIdOf { + PALLET_ID.into_sub_account(request_id) + } + + pub fn generate_request_id( + requester_id: &T::AccountId, + country: &[u8], + region: &[u8], + city: &[u8], + service_category: &[u8], + ) -> T::Hash { + let mut seed = requester_id.encode(); + let account_info = frame_system::Pallet::::account(requester_id); + + seed.append(&mut account_info.nonce.encode()); + seed.append(&mut country.encode()); + seed.append(&mut region.encode()); + seed.append(&mut city.encode()); + seed.append(&mut service_category.encode()); + + T::Hashing::hash(&seed) + } + + pub fn do_transfer( + sender: &T::AccountId, + receiver: &T::AccountId, + amount: BalanceOf, + existence: ExistenceRequirement, + ) -> Result<(), Error> { + let result = CurrencyOf::::transfer(sender, receiver, amount, existence); + + if let Err(dispatch) = result { + return match dispatch { + DispatchError::Other(_) => Err(Error::::Other), + DispatchError::CannotLookup => Err(Error::::CannotLookup), + DispatchError::BadOrigin => Err(Error::::BadOrigin), + DispatchError::TooManyConsumers => Err(Error::::TooManyConsumers), + DispatchError::ConsumerRemaining => Err(Error::::ConsumerRemaining), + DispatchError::NoProviders => Err(Error::::NoProviders), + DispatchError::Token(_) => Err(Error::::Token), + DispatchError::Arithmetic(_) => Err(Error::::Arithmetic), + DispatchError::Module(_) => Err(Error::::Arithmetic), + } + } + + Ok(()) + } +} diff --git a/pallets/service-request/src/impl_service_request.rs b/pallets/service-request/src/impl_service_request.rs new file mode 100644 index 00000000..1723bb16 --- /dev/null +++ b/pallets/service-request/src/impl_service_request.rs @@ -0,0 +1,353 @@ +use super::*; + +use frame_support::{sp_runtime::traits::Zero, traits::ExistenceRequirement}; + +/// Service Request Interface Implementation +impl SeviceRequestInterface for Pallet { + type Error = Error; + type Balance = BalanceOf; + type Request = RequestOf; + type Admin = AdminOf; + type ServiceOffer = ServiceOfferOf; + type ServiceInvoice = ServiceInvoiceOf; + type RequestId = RequestIdOf; + type RequesterId = RequesterIdOf; + type LabId = LabIdOf; + type Country = CountryOf; + type Region = RegionOf; + type City = CityOf; + type ServiceCategory = ServiceCategoryOf; + type ServiceId = ServiceIdOf; + type OrderId = OrderIdOf; + type DNASampleTrackingId = DNASampleTrackingIdOf; + + fn create_request( + requester_id: Self::RequesterId, + country: Self::Country, + region: Self::Region, + city: Self::City, + service_category: Self::ServiceCategory, + staking_amount: Self::Balance, + ) -> Result { + if staking_amount.is_zero() { + return Err(Error::::NotValidAmount) + } + + let request_id = + Self::generate_request_id(&requester_id, &country, ®ion, &city, &service_category); + + let now = T::TimeProvider::now().as_millis(); + + Self::do_transfer( + &requester_id, + &Self::staking_account_id(request_id), + staking_amount, + ExistenceRequirement::KeepAlive, + )?; + + let request = Request::new( + request_id, + &requester_id, + &country, + ®ion, + &city, + &service_category, + staking_amount, + now, + ); + + RequestById::::insert(&request_id, &request); + RequestByAccountId::::mutate(&requester_id, |request_ids| request_ids.push(request_id)); + StakingAccountIdByRequestId::::insert(&request_id, Self::staking_account_id(request_id)); + ServiceCountRequest::::mutate((country, region, city, service_category), |value| { + *value = value.wrapping_add(1); + }); + + Ok(request) + } + + fn unstake( + requester_id: Self::RequesterId, + request_id: Self::RequestId, + ) -> Result { + let mut request = RequestById::::get(request_id).ok_or(Error::::RequestNotFound)?; + + if request.requester_address != requester_id { + return Err(Error::::Unauthorized) + } + + if request.status != RequestStatus::Open { + return Err(Error::::RequestUnableToUnstake) + } + + let now: u128 = T::TimeProvider::now().as_millis(); + + request.status = RequestStatus::WaitingForUnstaked; + request.unstaked_at = Some(now); + + RequestById::::insert(request_id, &request); + + Ok(request) + } + + fn retrieve_unstaked_amount( + admin: Self::Admin, + request_id: Self::RequestId, + ) -> Result { + let _ = AdminKey::::get() + .filter(|account_id| account_id == &admin) + .ok_or(Error::::Unauthorized)?; + + let mut request = RequestById::::get(request_id).ok_or(Error::::RequestNotFound)?; + + if request.status != RequestStatus::WaitingForUnstaked { + return Err(Error::::RequestUnableToRetrieveUnstake) + } + + let requester_id = request.requester_address.clone(); + + let now: u128 = T::TimeProvider::now().as_millis(); + let unstaked_at: u128 = request.unstaked_at.unwrap(); + // TODO: move to constant runtime config + let six_days: u128 = 3600_u128 * 144_u128 * 1000_u128; + + if (now - unstaked_at) == six_days { + return Err(Error::::RequestWaitingForUnstaked) + } + + Self::do_transfer( + &Self::staking_account_id(request_id), + &requester_id, + request.staking_amount, + ExistenceRequirement::AllowDeath, + )?; + + Self::deposit_event(Event::StakingAmountRefunded( + requester_id, + request_id, + request.staking_amount, + )); + + request.status = RequestStatus::Unstaked; + + RequestById::::insert(request_id, &request); + + Ok(request) + } + + fn claim_request( + lab_id: Self::LabId, + request_id: Self::RequestId, + service_id: Self::ServiceId, + testing_price: Self::Balance, + qc_price: Self::Balance, + ) -> Result<(Self::Request, Self::ServiceOffer), Self::Error> { + let mut request = RequestById::::get(request_id).ok_or(Error::::RequestNotFound)?; + + if request.status == RequestStatus::WaitingForUnstaked || + request.status == RequestStatus::Unstaked + { + return Err(Error::::RequestAlreadyUnstaked) + } + + if request.status == RequestStatus::Claimed { + return Err(Error::::RequestAlreadyClaimed) + } + + if request.status != RequestStatus::Open { + return Err(Error::::RequestUnableToClaimed) + } + + let lab_status = + T::Labs::lab_verification_status(&lab_id).ok_or(Error::::LabNotFound)?; + let service_offer = + ServiceOffer::new(request_id, &lab_id, service_id, testing_price, qc_price); + + let now = T::TimeProvider::now().as_millis(); + + if lab_status.is_verified() { + request.status = RequestStatus::Claimed; + request.lab_address = Some(lab_id); + request.updated_at = Some(now); + + RequestById::::insert(request_id, &request); + ServiceOfferById::::insert(request_id, &service_offer); + } else { + RequestsByLabId::::try_mutate(lab_id, |value| { + if value.iter().any(|x| x == &request_id) { + return Err(Error::::RequestAlreadyInList) + } + + value.push(request_id); + + Ok(()) + })?; + } + + Ok((request, service_offer)) + } + + fn process_request( + requester_id: Self::RequesterId, + lab_id: Self::LabId, + request_id: Self::RequestId, + order_id: Self::OrderId, + dna_sample_tracking_id: Self::DNASampleTrackingId, + additional_staking_amount: Self::Balance, + ) -> Result { + let mut request = RequestById::::get(request_id).ok_or(Error::::RequestNotFound)?; + + if requester_id != request.requester_address { + return Err(Error::::Unauthorized) + } + + if request.status == RequestStatus::WaitingForUnstaked || + request.status == RequestStatus::Unstaked + { + return Err(Error::::RequestAlreadyUnstaked) + } + + let lab_address = request.get_lab_address().as_ref().ok_or(Error::::LabNotFound)?; + + if lab_address != &lab_id { + return Err(Error::::LabNotFound) + } + + if request.status != RequestStatus::Claimed { + return Err(Error::::RequestUnableToProccess) + } + + let service_offer = + ServiceOfferById::::get(request_id).ok_or(Error::::ServiceOfferNotFound)?; + let pay_amount = request.staking_amount; + let testing_price = service_offer.testing_price; + let qc_price = service_offer.qc_price; + let total_price = testing_price + qc_price; + + let mut final_pay_amount = total_price; + + if pay_amount > total_price { + let excess = pay_amount - total_price; + + Self::do_transfer( + &Self::staking_account_id(request_id), + &requester_id, + excess, + ExistenceRequirement::KeepAlive, + )?; + + Self::deposit_event(Event::StakingAmountExcessRefunded( + requester_id.clone(), + request_id, + excess, + )); + } else { + final_pay_amount = request.staking_amount + additional_staking_amount; + + if final_pay_amount != total_price { + return Err(Error::::NotValidAmount) + } + + Self::do_transfer( + &requester_id, + &Self::staking_account_id(request_id), + additional_staking_amount, + ExistenceRequirement::KeepAlive, + )?; + + Self::deposit_event(Event::StakingAmountIncreased( + requester_id.clone(), + request_id, + additional_staking_amount, + )); + } + + let service_invoice = ServiceInvoice::new( + request_id, + order_id, + service_offer.service_id, + requester_id, + lab_id, + dna_sample_tracking_id, + testing_price, + qc_price, + final_pay_amount, + ); + + let now = T::TimeProvider::now().as_millis(); + + request.status = RequestStatus::Processed; + request.updated_at = Some(now); + + RequestById::::insert(request_id, request); + ServiceInvoiceById::::insert(request_id, &service_invoice); + ServiceInvoiceByOrderId::::insert(order_id, &service_invoice); + + Ok(service_invoice) + } + + fn finalize_request( + admin: Self::Admin, + request_id: Self::RequestId, + test_result_success: bool, + ) -> Result { + let _ = AdminKey::::get() + .filter(|account_id| account_id == &admin) + .ok_or(Error::::Unauthorized)?; + + let mut request = RequestById::::get(request_id).ok_or(Error::::RequestNotFound)?; + let service_invoice = + ServiceInvoiceById::::get(request_id).ok_or(Error::::ServiceInvoiceNotFound)?; + + if request.status != RequestStatus::Processed { + return Err(Error::::RequestUnableToFinalize) + } + + let requester_id = service_invoice.customer_address.clone(); + let lab_id = service_invoice.seller_address.clone(); + + let mut pay_amount = service_invoice.pay_amount; + + if !test_result_success { + pay_amount = service_invoice.qc_price; + + let testing_price = service_invoice.testing_price; + + // Transfer testing price back to customer + Self::do_transfer( + &Self::staking_account_id(request_id), + &requester_id, + testing_price, + ExistenceRequirement::AllowDeath, + )?; + } + + // Transfer to lab_id + Self::do_transfer( + &Self::staking_account_id(request_id), + &lab_id, + pay_amount, + ExistenceRequirement::AllowDeath, + )?; + + let now = T::TimeProvider::now().as_millis(); + + request.status = RequestStatus::Finalized; + request.updated_at = Some(now); + + RequestById::::insert(request_id, &request); + + // Removed from customer request list + RequestByAccountId::::mutate(&requester_id, |request_ids| { + request_ids.retain(|&x| x != request_id); + }); + + // Update service count request + ServiceCountRequest::::mutate( + (&request.country, &request.region, &request.city, &request.service_category), + |value| *value = value.wrapping_sub(1), + ); + + Ok(service_invoice) + } +} diff --git a/pallets/service-request/src/interface.rs b/pallets/service-request/src/interface.rs index 3d391e6b..f36b056d 100644 --- a/pallets/service-request/src/interface.rs +++ b/pallets/service-request/src/interface.rs @@ -16,14 +16,6 @@ pub trait SeviceRequestInterface { type OrderId; type DNASampleTrackingId; - fn generate_request_id( - requester_id: Self::RequesterId, - country: Self::Country, - region: Self::Region, - city: Self::City, - service_category: Self::ServiceCategory, - ) -> Self::RequestId; - fn create_request( requester_id: Self::RequesterId, country: Self::Country, @@ -65,9 +57,4 @@ pub trait SeviceRequestInterface { request_id: Self::RequestId, test_result_success: bool, ) -> Result; - - fn update_admin_key( - account_id: &T::AccountId, - admin_key: &T::AccountId, - ) -> Result<(), Self::Error>; } diff --git a/pallets/service-request/src/lib.rs b/pallets/service-request/src/lib.rs index 28df5a46..dedbf93e 100644 --- a/pallets/service-request/src/lib.rs +++ b/pallets/service-request/src/lib.rs @@ -1,20 +1,16 @@ #![cfg_attr(not(feature = "std"), no_std)] +pub use pallet::*; + use frame_support::{ codec::{Decode, Encode}, dispatch::DispatchResultWithPostInfo, pallet_prelude::*, - scale_info::TypeInfo, - sp_runtime::{ - traits::{AccountIdConversion, Hash, Zero}, - RuntimeDebug, - }, + sp_runtime::{traits::Hash, RuntimeDebug}, sp_std::prelude::*, - traits::{Currency, ExistenceRequirement, UnixTime}, - PalletId, + traits::{Currency, UnixTime}, }; use frame_system::pallet_prelude::*; -pub use pallet::*; use primitives_verification_status::VerificationStatusTrait; use traits_labs::LabsProvider; @@ -24,154 +20,19 @@ mod mock; #[cfg(test)] mod tests; +pub mod functions; +pub mod impl_service_request; pub mod interface; -pub use interface::SeviceRequestInterface; - +pub mod types; pub mod weights; -pub use weights::WeightInfo; - -#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo)] -pub enum RequestStatus { - Open, - WaitingForUnstaked, - Unstaked, - Claimed, - Processed, - Finalized, -} -impl Default for RequestStatus { - fn default() -> Self { - RequestStatus::Open - } -} - -#[derive(Clone, Decode, Default, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)] -pub struct Request { - pub hash: Hash, - pub requester_address: AccountId, - pub lab_address: Option, - pub country: Vec, - pub region: Vec, - pub city: Vec, - pub service_category: Vec, - pub staking_amount: Balance, - pub status: RequestStatus, - pub created_at: u128, - pub updated_at: Option, - pub unstaked_at: Option, -} -#[allow(clippy::too_many_arguments)] -impl Request { - pub fn new( - hash: Hash, - requester_address: AccountId, - country: Vec, - region: Vec, - city: Vec, - service_category: Vec, - staking_amount: Balance, - created_at: u128, - ) -> Self { - Self { - hash, - requester_address, - lab_address: None, - country, - region, - city, - service_category, - staking_amount, - status: RequestStatus::default(), - created_at, - updated_at: None, - unstaked_at: None, - } - } -} - -#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)] -pub struct ServiceOffer { - pub request_hash: Hash, - pub lab_address: AccountId, - pub service_id: Hash, - pub testing_price: Balance, - pub qc_price: Balance, -} -impl ServiceOffer { - pub fn new( - request_hash: Hash, - lab_address: AccountId, - service_id: Hash, - testing_price: Balance, - qc_price: Balance, - ) -> Self { - Self { request_hash, lab_address, service_id, testing_price, qc_price } - } -} -#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)] -pub struct ServiceInvoice { - pub request_hash: Hash, - pub order_id: Hash, - pub service_id: Hash, - pub customer_address: AccountId, - pub seller_address: AccountId, - pub dna_sample_tracking_id: Vec, - pub testing_price: Balance, - pub qc_price: Balance, - pub pay_amount: Balance, -} -#[allow(clippy::too_many_arguments)] -impl ServiceInvoice { - pub fn new( - request_hash: Hash, - order_id: Hash, - service_id: Hash, - customer_address: AccountId, - seller_address: AccountId, - dna_sample_tracking_id: Vec, - testing_price: Balance, - qc_price: Balance, - pay_amount: Balance, - ) -> Self { - Self { - request_hash, - order_id, - service_id, - customer_address, - seller_address, - dna_sample_tracking_id, - testing_price, - qc_price, - pay_amount, - } - } -} +pub use interface::SeviceRequestInterface; +pub use types::*; +pub use weights::WeightInfo; #[frame_support::pallet] pub mod pallet { - use crate::*; - - pub const PALLET_ID: PalletId = PalletId(*b"reqsrvc!"); - - pub type AccountIdOf = ::AccountId; - pub type CurrencyOf = ::Currency; - pub type BalanceOf = as Currency>>::Balance; - pub type HashOf = ::Hash; - pub type AdminOf = AccountIdOf; - pub type RequestOf = Request, BalanceOf, HashOf>; - pub type ServiceOfferOf = ServiceOffer, BalanceOf, HashOf>; - pub type ServiceInvoiceOf = ServiceInvoice, BalanceOf, HashOf>; - pub type RequestIdOf = HashOf; - pub type RequesterIdOf = AccountIdOf; - pub type LabIdOf = AccountIdOf; - pub type CountryOf = Vec; - pub type RegionOf = Vec; - pub type CityOf = Vec; - pub type ServiceCategoryOf = Vec; - pub type ServiceIdOf = HashOf; - pub type OrderIdOf = HashOf; - pub type DNASampleTrackingIdOf = Vec; + use super::*; #[pallet::config] pub trait Config: frame_system::Config { @@ -476,546 +337,3 @@ pub mod pallet { } } } - -/// Pallet Methods -impl Pallet { - pub fn staking_account_id(request_id: ServiceIdOf) -> AccountIdOf { - PALLET_ID.into_sub_account(request_id) - } -} - -/// Service Request Interface Implementation -impl SeviceRequestInterface for Pallet { - type Error = Error; - type Balance = BalanceOf; - type Request = RequestOf; - type Admin = AdminOf; - type ServiceOffer = ServiceOfferOf; - type ServiceInvoice = ServiceInvoiceOf; - type RequestId = RequestIdOf; - type RequesterId = RequesterIdOf; - type LabId = LabIdOf; - type Country = CountryOf; - type Region = RegionOf; - type City = CityOf; - type ServiceCategory = ServiceCategoryOf; - type ServiceId = ServiceIdOf; - type OrderId = OrderIdOf; - type DNASampleTrackingId = DNASampleTrackingIdOf; - - fn generate_request_id( - requester_id: Self::RequesterId, - country: Self::Country, - region: Self::Region, - city: Self::City, - service_category: Self::ServiceCategory, - ) -> Self::RequestId { - let mut seed = requester_id.encode(); - let account_info = frame_system::Pallet::::account(requester_id); - - seed.append(&mut account_info.nonce.encode()); - seed.append(&mut country.encode()); - seed.append(&mut region.encode()); - seed.append(&mut city.encode()); - seed.append(&mut service_category.encode()); - - T::Hashing::hash(&seed) - } - - fn create_request( - requester_id: Self::RequesterId, - country: Self::Country, - region: Self::Region, - city: Self::City, - service_category: Self::ServiceCategory, - staking_amount: Self::Balance, - ) -> Result { - if staking_amount.is_zero() { - return Err(Error::::NotValidAmount) - } - - let request_id = Self::generate_request_id( - requester_id.clone(), - country.clone(), - region.clone(), - city.clone(), - service_category.clone(), - ); - - let now = T::TimeProvider::now().as_millis(); - - match CurrencyOf::::transfer( - &requester_id, - &Self::staking_account_id(request_id), - staking_amount, - ExistenceRequirement::KeepAlive, - ) { - Ok(_) => { - let request = Request::new( - request_id, - requester_id.clone(), - country.clone(), - region.clone(), - city.clone(), - service_category.clone(), - staking_amount, - now, - ); - - let mut request_ids = RequestByAccountId::::get(requester_id.clone()); - request_ids.push(request_id); - - StakingAccountIdByRequestId::::insert( - request_id, - Self::staking_account_id(request_id), - ); - RequestByAccountId::::insert(requester_id.clone(), request_ids); - RequestById::::insert(request_id, request.clone()); - - let service_count = ServiceCountRequest::::get(( - country.clone(), - region.clone(), - city.clone(), - service_category.clone(), - )); - - ServiceCountRequest::::insert( - (country, region, city, service_category), - service_count.wrapping_add(1), - ); - - Ok(request) - }, - Err(dispatch) => match dispatch { - sp_runtime::DispatchError::Other(_) => Err(Error::::Other), - sp_runtime::DispatchError::CannotLookup => Err(Error::::CannotLookup), - sp_runtime::DispatchError::BadOrigin => Err(Error::::BadOrigin), - sp_runtime::DispatchError::TooManyConsumers => Err(Error::::TooManyConsumers), - sp_runtime::DispatchError::ConsumerRemaining => Err(Error::::ConsumerRemaining), - sp_runtime::DispatchError::NoProviders => Err(Error::::NoProviders), - sp_runtime::DispatchError::Token(_) => Err(Error::::Token), - sp_runtime::DispatchError::Arithmetic(_) => Err(Error::::Arithmetic), - sp_runtime::DispatchError::Module(_) => Err(Error::::Arithmetic), - }, - } - } - - fn unstake( - requester_id: Self::RequesterId, - request_id: Self::RequestId, - ) -> Result { - let request = RequestById::::get(request_id); - - if request.is_none() { - return Err(Error::::RequestNotFound) - } - - let mut request = request.unwrap(); - - if request.requester_address != requester_id { - return Err(Error::::Unauthorized) - } - - if request.status != RequestStatus::Open { - return Err(Error::::RequestUnableToUnstake) - } - - request.status = RequestStatus::WaitingForUnstaked; - let now: u128 = T::TimeProvider::now().as_millis(); - request.unstaked_at = Some(now); - - RequestById::::insert(request_id, request.clone()); - - Ok(request) - } - - fn retrieve_unstaked_amount( - admin: Self::Admin, - request_id: Self::RequestId, - ) -> Result { - if admin != AdminKey::::get().unwrap() { - return Err(Error::::Unauthorized) - } - - let request = RequestById::::get(request_id); - - if request.is_none() { - return Err(Error::::RequestNotFound) - } - - let mut request = request.unwrap(); - - if request.status != RequestStatus::WaitingForUnstaked { - return Err(Error::::RequestUnableToRetrieveUnstake) - } - - let requester_id = request.requester_address.clone(); - - let now: u128 = T::TimeProvider::now().as_millis(); - let unstaked_at: u128 = request.unstaked_at.unwrap(); - // TODO: move to constant runtime config - let six_days: u128 = 3600_u128 * 144_u128 * 1000_u128; - - if (now - unstaked_at) == six_days { - return Err(Error::::RequestWaitingForUnstaked) - } - - match CurrencyOf::::transfer( - &Self::staking_account_id(request_id), - &requester_id, - request.staking_amount, - ExistenceRequirement::AllowDeath, - ) { - Ok(_) => { - Self::deposit_event(Event::StakingAmountRefunded( - requester_id, - request_id, - request.staking_amount, - )); - - request.status = RequestStatus::Unstaked; - - RequestById::::insert(request_id, request.clone()); - - Ok(request) - }, - Err(dispatch) => match dispatch { - sp_runtime::DispatchError::Other(_) => Err(Error::::Other), - sp_runtime::DispatchError::CannotLookup => Err(Error::::CannotLookup), - sp_runtime::DispatchError::BadOrigin => Err(Error::::BadOrigin), - sp_runtime::DispatchError::TooManyConsumers => Err(Error::::TooManyConsumers), - sp_runtime::DispatchError::ConsumerRemaining => Err(Error::::ConsumerRemaining), - sp_runtime::DispatchError::NoProviders => Err(Error::::NoProviders), - sp_runtime::DispatchError::Token(_) => Err(Error::::Token), - sp_runtime::DispatchError::Arithmetic(_) => Err(Error::::Arithmetic), - sp_runtime::DispatchError::Module(_) => Err(Error::::Arithmetic), - }, - } - } - - fn claim_request( - lab_id: Self::LabId, - request_id: Self::RequestId, - service_id: Self::ServiceId, - testing_price: Self::Balance, - qc_price: Self::Balance, - ) -> Result<(Self::Request, Self::ServiceOffer), Self::Error> { - let request = RequestById::::get(request_id); - - if request.is_none() { - return Err(Error::::RequestNotFound) - } - - let mut request = request.unwrap(); - - if request.status == RequestStatus::WaitingForUnstaked || - request.status == RequestStatus::Unstaked - { - return Err(Error::::RequestAlreadyUnstaked) - } - - if request.status == RequestStatus::Claimed { - return Err(Error::::RequestAlreadyClaimed) - } - - if request.status != RequestStatus::Open { - return Err(Error::::RequestUnableToClaimed) - } - - let lab_status = T::Labs::lab_verification_status(&lab_id); - - if lab_status.is_none() { - return Err(Error::::LabNotFound) - } - - let lab_status = lab_status.unwrap(); - - let service_offer = - ServiceOffer::new(request_id, lab_id.clone(), service_id, testing_price, qc_price); - - let now = T::TimeProvider::now().as_millis(); - - if lab_status.is_verified() { - request.status = RequestStatus::Claimed; - request.lab_address = Some(lab_id); - request.updated_at = Some(now); - - RequestById::::insert(request_id, request.clone()); - ServiceOfferById::::insert(request_id, service_offer.clone()); - } else { - let mut request_ids = RequestsByLabId::::get(lab_id.clone()); - let found = request_ids.iter().find(|x| x == &&request_id); - - if found.is_some() { - return Err(Error::::RequestAlreadyInList) - } - - request_ids.push(request_id); - - RequestsByLabId::::insert(lab_id, request_ids); - } - - Ok((request, service_offer)) - } - - fn process_request( - requester_id: Self::RequesterId, - lab_id: Self::LabId, - request_id: Self::RequestId, - order_id: Self::OrderId, - dna_sample_tracking_id: Self::DNASampleTrackingId, - additional_staking_amount: Self::Balance, - ) -> Result { - let request = RequestById::::get(request_id); - - if request.is_none() { - return Err(Error::::RequestNotFound) - } - - let mut request = request.unwrap(); - - if requester_id != request.requester_address { - return Err(Error::::Unauthorized) - } - - if request.status == RequestStatus::WaitingForUnstaked || - request.status == RequestStatus::Unstaked - { - return Err(Error::::RequestAlreadyUnstaked) - } - - if request.lab_address.is_none() { - return Err(Error::::LabNotFound) - } - - if request.status != RequestStatus::Claimed { - return Err(Error::::RequestUnableToProccess) - } - - if request.lab_address.clone().unwrap() != lab_id { - return Err(Error::::LabNotFound) - } - - let service_offer = ServiceOfferById::::get(request_id).unwrap(); - let pay_amount = request.staking_amount; - let testing_price = service_offer.testing_price; - let qc_price = service_offer.qc_price; - let total_price = testing_price + qc_price; - let mut excess = Zero::zero(); - let mut final_pay_amount = pay_amount; - - if pay_amount > total_price { - excess = pay_amount - total_price; - final_pay_amount = total_price; - } - - if !excess.is_zero() { - match CurrencyOf::::transfer( - &Self::staking_account_id(request_id), - &requester_id, - excess, - ExistenceRequirement::KeepAlive, - ) { - Ok(_) => { - Self::deposit_event(Event::StakingAmountExcessRefunded( - requester_id.clone(), - request_id, - excess, - )); - }, - Err(dispatch) => match dispatch { - sp_runtime::DispatchError::Other(_) => return Err(Error::::Other), - sp_runtime::DispatchError::CannotLookup => return Err(Error::::CannotLookup), - sp_runtime::DispatchError::BadOrigin => return Err(Error::::BadOrigin), - sp_runtime::DispatchError::TooManyConsumers => - return Err(Error::::TooManyConsumers), - sp_runtime::DispatchError::ConsumerRemaining => - return Err(Error::::ConsumerRemaining), - sp_runtime::DispatchError::NoProviders => return Err(Error::::NoProviders), - sp_runtime::DispatchError::Token(_) => return Err(Error::::Token), - sp_runtime::DispatchError::Arithmetic(_) => return Err(Error::::Arithmetic), - sp_runtime::DispatchError::Module(_) => return Err(Error::::Arithmetic), - }, - } - } else { - final_pay_amount = request.staking_amount + additional_staking_amount; - - if final_pay_amount != total_price { - return Err(Error::::NotValidAmount) - } else { - match CurrencyOf::::transfer( - &requester_id, - &Self::staking_account_id(request_id), - additional_staking_amount, - ExistenceRequirement::KeepAlive, - ) { - Ok(_) => { - Self::deposit_event(Event::StakingAmountIncreased( - requester_id.clone(), - request_id, - additional_staking_amount, - )); - }, - Err(dispatch) => match dispatch { - sp_runtime::DispatchError::Other(_) => return Err(Error::::Other), - sp_runtime::DispatchError::CannotLookup => - return Err(Error::::CannotLookup), - sp_runtime::DispatchError::BadOrigin => return Err(Error::::BadOrigin), - sp_runtime::DispatchError::TooManyConsumers => - return Err(Error::::TooManyConsumers), - sp_runtime::DispatchError::ConsumerRemaining => - return Err(Error::::ConsumerRemaining), - sp_runtime::DispatchError::NoProviders => - return Err(Error::::NoProviders), - sp_runtime::DispatchError::Token(_) => return Err(Error::::Token), - sp_runtime::DispatchError::Arithmetic(_) => - return Err(Error::::Arithmetic), - sp_runtime::DispatchError::Module(_) => return Err(Error::::Arithmetic), - }, - } - } - } - - let service_invoice = ServiceInvoice::new( - request_id, - order_id, - service_offer.service_id, - requester_id, - lab_id, - dna_sample_tracking_id, - testing_price, - qc_price, - final_pay_amount, - ); - - let now = T::TimeProvider::now().as_millis(); - - ServiceInvoiceById::::insert(request_id, service_invoice.clone()); - ServiceInvoiceByOrderId::::insert(order_id, service_invoice.clone()); - - request.status = RequestStatus::Processed; - request.updated_at = Some(now); - RequestById::::insert(request_id, request); - - Ok(service_invoice) - } - - fn finalize_request( - admin: Self::Admin, - request_id: Self::RequestId, - test_result_success: bool, - ) -> Result { - if admin != AdminKey::::get().unwrap() { - return Err(Error::::Unauthorized) - } - - let service_invoice = ServiceInvoiceById::::get(request_id); - - if service_invoice.is_none() { - return Err(Error::::ServiceInvoiceNotFound) - } - - let service_invoice = service_invoice.unwrap(); - - let mut request = RequestById::::get(request_id).unwrap(); - - if request.status != RequestStatus::Processed { - return Err(Error::::RequestUnableToFinalize) - } - - let requester_id = service_invoice.customer_address.clone(); - let lab_id = service_invoice.seller_address.clone(); - - let mut pay_amount = service_invoice.pay_amount; - - if !test_result_success { - pay_amount = service_invoice.qc_price; - - let testing_price = service_invoice.testing_price; - - // Transfer testing price back to customer - match CurrencyOf::::transfer( - &Self::staking_account_id(request_id), - &requester_id, - testing_price, - ExistenceRequirement::AllowDeath, - ) { - Ok(_) => (), - Err(dispatch) => match dispatch { - sp_runtime::DispatchError::Other(_) => return Err(Error::::Other), - sp_runtime::DispatchError::CannotLookup => return Err(Error::::CannotLookup), - sp_runtime::DispatchError::BadOrigin => return Err(Error::::BadOrigin), - sp_runtime::DispatchError::TooManyConsumers => - return Err(Error::::TooManyConsumers), - sp_runtime::DispatchError::ConsumerRemaining => - return Err(Error::::ConsumerRemaining), - sp_runtime::DispatchError::NoProviders => return Err(Error::::NoProviders), - sp_runtime::DispatchError::Token(_) => return Err(Error::::Token), - sp_runtime::DispatchError::Arithmetic(_) => return Err(Error::::Arithmetic), - sp_runtime::DispatchError::Module(_) => return Err(Error::::Arithmetic), - }, - } - } - - // Transfer to lab_id - match CurrencyOf::::transfer( - &Self::staking_account_id(request_id), - &lab_id, - pay_amount, - ExistenceRequirement::AllowDeath, - ) { - Ok(_) => (), - Err(dispatch) => match dispatch { - sp_runtime::DispatchError::Other(_) => return Err(Error::::Other), - sp_runtime::DispatchError::CannotLookup => return Err(Error::::CannotLookup), - sp_runtime::DispatchError::BadOrigin => return Err(Error::::BadOrigin), - sp_runtime::DispatchError::TooManyConsumers => - return Err(Error::::TooManyConsumers), - sp_runtime::DispatchError::ConsumerRemaining => - return Err(Error::::ConsumerRemaining), - sp_runtime::DispatchError::NoProviders => return Err(Error::::NoProviders), - sp_runtime::DispatchError::Token(_) => return Err(Error::::Token), - sp_runtime::DispatchError::Arithmetic(_) => return Err(Error::::Arithmetic), - sp_runtime::DispatchError::Module(_) => return Err(Error::::Arithmetic), - }, - } - - let now = T::TimeProvider::now().as_millis(); - - // Removed from customer request list - let mut request_ids = RequestByAccountId::::get(requester_id.clone()); - request_ids.retain(|&x| x != request_id); - RequestByAccountId::::insert(requester_id, request_ids); - - // Update service count request - let service_count_request = ServiceCountRequest::::get(( - &request.country, - &request.region, - &request.city, - &request.service_category, - )); - ServiceCountRequest::::insert( - (&request.country, &request.region, &request.city, &request.service_category), - service_count_request.wrapping_sub(1), - ); - - request.status = RequestStatus::Finalized; - request.updated_at = Some(now); - RequestById::::insert(request_id, request); - - Ok(service_invoice) - } - - fn update_admin_key( - account_id: &T::AccountId, - admin_key: &T::AccountId, - ) -> Result<(), Self::Error> { - if account_id.clone() != AdminKey::::get().unwrap() { - return Err(Error::::Unauthorized) - } - - AdminKey::::put(admin_key); - - Ok(()) - } -} diff --git a/pallets/service-request/src/types.rs b/pallets/service-request/src/types.rs new file mode 100644 index 00000000..2517d50c --- /dev/null +++ b/pallets/service-request/src/types.rs @@ -0,0 +1,150 @@ +use crate::*; + +use frame_support::traits::Currency; +use scale_info::TypeInfo; +use sp_std::vec::Vec; + +pub type AccountIdOf = ::AccountId; +pub type CurrencyOf = ::Currency; +pub type BalanceOf = as Currency>>::Balance; +pub type HashOf = ::Hash; +pub type AdminOf = AccountIdOf; +pub type RequestOf = Request, BalanceOf, HashOf>; +pub type ServiceOfferOf = ServiceOffer, BalanceOf, HashOf>; +pub type ServiceInvoiceOf = ServiceInvoice, BalanceOf, HashOf>; +pub type RequestIdOf = HashOf; +pub type RequesterIdOf = AccountIdOf; +pub type LabIdOf = AccountIdOf; +pub type CountryOf = Vec; +pub type RegionOf = Vec; +pub type CityOf = Vec; +pub type ServiceCategoryOf = Vec; +pub type ServiceIdOf = HashOf; +pub type OrderIdOf = HashOf; +pub type DNASampleTrackingIdOf = Vec; + +#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo)] +pub enum RequestStatus { + Open, + WaitingForUnstaked, + Unstaked, + Claimed, + Processed, + Finalized, +} +impl Default for RequestStatus { + fn default() -> Self { + RequestStatus::Open + } +} + +#[derive(Clone, Decode, Default, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)] +pub struct Request { + pub hash: Hash, + pub requester_address: AccountId, + pub lab_address: Option, + pub country: Vec, + pub region: Vec, + pub city: Vec, + pub service_category: Vec, + pub staking_amount: Balance, + pub status: RequestStatus, + pub created_at: u128, + pub updated_at: Option, + pub unstaked_at: Option, +} +#[allow(clippy::too_many_arguments)] +impl Request { + pub fn new( + hash: Hash, + requester_address: &AccountId, + country: &[u8], + region: &[u8], + city: &[u8], + service_category: &[u8], + staking_amount: Balance, + created_at: u128, + ) -> Self { + Self { + hash, + requester_address: requester_address.clone(), + lab_address: None, + country: country.to_vec(), + region: region.to_vec(), + city: city.to_vec(), + service_category: service_category.to_vec(), + staking_amount, + status: RequestStatus::default(), + created_at, + updated_at: None, + unstaked_at: None, + } + } + + pub fn get_lab_address(&self) -> &Option { + &self.lab_address + } + + pub fn get_requester_address(&self) -> &AccountId { + &self.requester_address + } +} + +#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)] +pub struct ServiceOffer { + pub request_hash: Hash, + pub lab_address: AccountId, + pub service_id: Hash, + pub testing_price: Balance, + pub qc_price: Balance, +} +impl ServiceOffer { + pub fn new( + request_hash: Hash, + lab_address: &AccountId, + service_id: Hash, + testing_price: Balance, + qc_price: Balance, + ) -> Self { + Self { request_hash, lab_address: lab_address.clone(), service_id, testing_price, qc_price } + } +} + +#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)] +pub struct ServiceInvoice { + pub request_hash: Hash, + pub order_id: Hash, + pub service_id: Hash, + pub customer_address: AccountId, + pub seller_address: AccountId, + pub dna_sample_tracking_id: Vec, + pub testing_price: Balance, + pub qc_price: Balance, + pub pay_amount: Balance, +} +#[allow(clippy::too_many_arguments)] +impl ServiceInvoice { + pub fn new( + request_hash: Hash, + order_id: Hash, + service_id: Hash, + customer_address: AccountId, + seller_address: AccountId, + dna_sample_tracking_id: Vec, + testing_price: Balance, + qc_price: Balance, + pay_amount: Balance, + ) -> Self { + Self { + request_hash, + order_id, + service_id, + customer_address, + seller_address, + dna_sample_tracking_id, + testing_price, + qc_price, + pay_amount, + } + } +} From ad617505bfb7cdd7435e4a2169b2a5393fd27943 Mon Sep 17 00:00:00 2001 From: abdulhakim2902 Date: Mon, 19 Sep 2022 12:46:15 +0700 Subject: [PATCH 2/2] fix: allow sudo change admin key --- pallets/service-request/src/lib.rs | 14 ++++++-------- pallets/service-request/src/tests.rs | 22 ++++++++++++++++------ 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/pallets/service-request/src/lib.rs b/pallets/service-request/src/lib.rs index dedbf93e..a5abd716 100644 --- a/pallets/service-request/src/lib.rs +++ b/pallets/service-request/src/lib.rs @@ -325,15 +325,13 @@ pub mod pallet { origin: OriginFor, account_id: T::AccountId, ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; + ensure_root(origin)?; - match >::update_admin_key(&who, &account_id) { - Ok(_) => { - Self::deposit_event(Event::UpdateServiceRequestAdminKeySuccessful(who.clone())); - Ok(().into()) - }, - Err(error) => Err(error.into()), - } + AdminKey::::put(&account_id); + + Self::deposit_event(Event::UpdateServiceRequestAdminKeySuccessful(account_id)); + + Ok(().into()) } } } diff --git a/pallets/service-request/src/tests.rs b/pallets/service-request/src/tests.rs index 58307a7f..d70c6104 100644 --- a/pallets/service-request/src/tests.rs +++ b/pallets/service-request/src/tests.rs @@ -1203,13 +1203,23 @@ fn cant_finalize_request_when_unauthorized() { #[test] fn cant_finalize_request_when_invoice_not_exist() { ::default().existential_deposit(0).build().execute_with(|| { + assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 100, 0)); AdminKey::::put(1); + + // Customer create request + assert_ok!(ServiceRequest::create_request( + Origin::signed(1), + String::from("Indonesia").into_bytes(), + String::from("West Java").into_bytes(), + String::from("Bogor").into_bytes(), + String::from("Vaksin").into_bytes(), + 10 + )); + + let request_id = ServiceRequest::request_by_account_id(1)[0]; + assert_noop!( - ServiceRequest::finalize_request( - Origin::signed(1), - Keccak256::hash("request_id".as_bytes()), - true - ), + ServiceRequest::finalize_request(Origin::signed(1), request_id, true), Error::::ServiceInvoiceNotFound ); }) @@ -1445,7 +1455,7 @@ fn update_admin_key_works() { assert_eq!(ServiceRequest::admin_key(), Some(2)); - assert_ok!(ServiceRequest::update_admin_key(Origin::signed(2), 1,)); + assert_ok!(ServiceRequest::update_admin_key(Origin::root(), 1)); assert_eq!(ServiceRequest::admin_key(), Some(1)); })