-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add initial logistics app chain functionality
- Loading branch information
1 parent
c5945c1
commit 791ab86
Showing
26 changed files
with
905 additions
and
132 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
[package] | ||
name = "pallet-carrier" | ||
version = "4.0.0-dev" | ||
description = "FRAME pallet for carrier runtime logic." | ||
authors = ["Substrate DevHub <https://github.com/substrate-developer-hub>"] | ||
homepage = "https://substrate.io" | ||
edition = "2021" | ||
license = "MIT-0" | ||
publish = false | ||
repository = "https://github.com/aaronbassett/Logistics-dApp" | ||
|
||
[package.metadata.docs.rs] | ||
targets = ["x86_64-unknown-linux-gnu"] | ||
|
||
[dependencies] | ||
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ | ||
"derive", | ||
] } | ||
scale-info = { version = "2.5.0", default-features = false, features = [ | ||
"derive", | ||
] } | ||
frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } | ||
frame-support = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } | ||
frame-system = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } | ||
sp-std = { version = "8.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } | ||
# pallet-package = { path = '../package', default-features = false } | ||
|
||
[dev-dependencies] | ||
sp-core = { version = "21.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } | ||
sp-io = { version = "23.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } | ||
sp-runtime = { version = "24.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } | ||
|
||
[features] | ||
default = ["std"] | ||
std = [ | ||
"codec/std", | ||
"frame-benchmarking?/std", | ||
"frame-support/std", | ||
"frame-system/std", | ||
"scale-info/std", | ||
] | ||
runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] | ||
try-runtime = ["frame-support/try-runtime"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
License: MIT-0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
use frame_support::pallet_macros::*; | ||
|
||
#[pallet_section] | ||
mod calls { | ||
|
||
#[pallet::call] | ||
impl<T: Config> Pallet<T> { | ||
#[pallet::call_index(0)] | ||
#[pallet::weight(10_000 + T::DbWeight::get().writes(1).ref_time())] | ||
pub fn proposal_create( | ||
origin: OriginFor<T>, | ||
client: T::AccountId, | ||
package_id: PackageId, | ||
maximum_fee_amount: u128, | ||
minimum_fee_amount: u128, | ||
penalty_period: u32, | ||
penalty_amount: u128, | ||
) -> DispatchResult { | ||
let who = ensure_signed(origin)?; | ||
|
||
// Can only submit 1 proposal at a time | ||
ensure!( | ||
!Proposals::<T>::contains_key((&client, &package_id, &who)), | ||
Error::<T>::ProposalExists | ||
); | ||
|
||
// Insert new proposal into storage | ||
Proposals::<T>::insert( | ||
(&client, &package_id, &who), | ||
Proposal::new( | ||
package_id, | ||
client.clone(), | ||
who.clone(), | ||
maximum_fee_amount, | ||
minimum_fee_amount, | ||
penalty_period, | ||
penalty_amount, | ||
), | ||
); | ||
|
||
Self::deposit_event(Event::ProposalCreated { | ||
client, | ||
package: package_id, | ||
carrier: who, | ||
maximum_fee: maximum_fee_amount, | ||
minimum_fee: minimum_fee_amount, | ||
}); | ||
|
||
Ok(()) | ||
} | ||
|
||
#[pallet::call_index(10)] | ||
#[pallet::weight(10_000 + T::DbWeight::get().writes(1).ref_time())] | ||
pub fn proposal_accept( | ||
origin: OriginFor<T>, | ||
package_id: PackageId, | ||
carrier: T::AccountId, | ||
) -> DispatchResult { | ||
let who = ensure_signed(origin)?; | ||
|
||
// Ensure proposal exists and is valid to be accepted | ||
ensure!( | ||
Self::proposal_is_valid(&who, &package_id, &carrier), | ||
Error::<T>::InvalidProposal | ||
); | ||
|
||
// Reject all proposals | ||
for package_proposal in Proposals::<T>::iter_prefix((&who, &package_id)) { | ||
let mut rejected_proposal = package_proposal.1; | ||
rejected_proposal.status = ProposalStatus::Rejected; | ||
Proposals::<T>::insert( | ||
(&who, &package_id, &rejected_proposal.carrier), | ||
rejected_proposal.clone(), | ||
); | ||
} | ||
|
||
// Mark proposal as accepted | ||
let mut proposal = Proposals::<T>::get((&who, &package_id, &carrier)) | ||
.ok_or(Error::<T>::ProposalDoesNotExist)?; | ||
proposal.status = ProposalStatus::Accepted; | ||
Proposals::<T>::insert((&who, &package_id, &carrier), proposal); | ||
|
||
// Send proposal accepted event | ||
Self::deposit_event(Event::ProposalAccepted { | ||
client: who.clone(), | ||
package: package_id, | ||
carrier: carrier.clone(), | ||
}); | ||
|
||
Ok(()) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
use frame_support::pallet_macros::*; | ||
|
||
#[pallet_section] | ||
mod config { | ||
|
||
#[pallet::config] | ||
pub trait Config: frame_system::Config { | ||
/// Because this pallet emits events, it depends on the runtime's definition of an event. | ||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
use frame_support::pallet_macros::*; | ||
|
||
#[pallet_section] | ||
mod errors { | ||
#[pallet::error] | ||
pub enum Error<T> { | ||
/// A proposal already exists from that carrier | ||
ProposalExists, | ||
/// A proposal does not exist from that carrier | ||
ProposalDoesNotExist, | ||
/// Proposal is not valid for requested action | ||
InvalidProposal, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
use frame_support::pallet_macros::*; | ||
|
||
#[pallet_section] | ||
mod events { | ||
#[pallet::event] | ||
#[pallet::generate_deposit(pub(super) fn deposit_event)] | ||
pub enum Event<T: Config> { | ||
/// New proposal created [Client ID, Package ID, Carrier ID, Max Fee, Min Fee] | ||
ProposalCreated { | ||
client: T::AccountId, | ||
package: PackageId, | ||
carrier: T::AccountId, | ||
maximum_fee: u128, | ||
minimum_fee: u128, | ||
}, | ||
/// Proposal accepted [Client ID, Package ID, Carrier ID] | ||
ProposalAccepted { client: T::AccountId, package: PackageId, carrier: T::AccountId }, | ||
/// Proposal rejected [Client ID, Package ID, Carrier ID] | ||
ProposalRejected { client: T::AccountId, package: PackageId, carrier: T::AccountId }, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
#![cfg_attr(not(feature = "std"), no_std)] | ||
|
||
pub use pallet::*; | ||
|
||
mod calls; | ||
mod config; | ||
mod errors; | ||
mod events; | ||
pub mod types; | ||
|
||
use crate::types::*; | ||
use frame_support::{pallet_macros::*, pallet_prelude::*, sp_runtime::SaturatedConversion}; | ||
use frame_system::pallet_prelude::*; | ||
|
||
#[import_section(events::events)] | ||
#[import_section(errors::errors)] | ||
#[import_section(config::config)] | ||
#[import_section(calls::calls)] | ||
#[frame_support::pallet] | ||
pub mod pallet { | ||
use super::*; | ||
|
||
#[pallet::pallet] | ||
pub struct Pallet<T>(_); | ||
|
||
#[pallet::storage] | ||
#[pallet::getter(fn get_proposal)] | ||
pub type Proposals<T: Config> = StorageNMap< | ||
_, | ||
( | ||
NMapKey<Blake2_128Concat, T::AccountId>, // Client's account id | ||
NMapKey<Blake2_128Concat, PackageId>, | ||
NMapKey<Blake2_128Concat, T::AccountId>, // Carrier's account id | ||
), | ||
Proposal<T>, | ||
>; | ||
} | ||
|
||
impl<T: Config> Pallet<T> { | ||
pub fn proposal_is_valid( | ||
client: &T::AccountId, | ||
package_id: &PackageId, | ||
carrier: &T::AccountId, | ||
) -> bool { | ||
let proposal = Self::get_proposal((&client, &package_id, &carrier)); | ||
proposal.is_some() && proposal.unwrap().status == ProposalStatus::Proposed | ||
} | ||
|
||
pub fn proposal_maximum_fee_amount( | ||
client: &T::AccountId, | ||
package_id: &PackageId, | ||
carrier: &T::AccountId, | ||
) -> Option<u128> { | ||
match Self::get_proposal((&client, &package_id, &carrier)) { | ||
Some(proposal) => Some(proposal.maximum_fee_amount), | ||
None => None, | ||
} | ||
} | ||
|
||
pub fn reject_proposals(client: &T::AccountId, package_id: &PackageId, carrier: &T::AccountId) { | ||
for package_proposal in Proposals::<T>::iter_prefix((&client, &package_id)) { | ||
let mut rejected_proposal = package_proposal.1; | ||
if rejected_proposal.carrier != *carrier { | ||
rejected_proposal.status = ProposalStatus::Rejected; | ||
Proposals::<T>::insert( | ||
(&client, &package_id, &rejected_proposal.carrier), | ||
rejected_proposal.clone(), | ||
); | ||
|
||
Self::deposit_event(Event::<T>::ProposalRejected { | ||
client: client.clone(), | ||
package: package_id.clone(), | ||
carrier: rejected_proposal.carrier, | ||
}); | ||
} | ||
} | ||
} | ||
|
||
pub fn accept_proposal( | ||
client: &T::AccountId, | ||
package_id: &PackageId, | ||
carrier: &T::AccountId, | ||
) -> DispatchResult { | ||
let mut proposal = Proposals::<T>::get((&client, &package_id, &carrier)) | ||
.ok_or(Error::<T>::ProposalDoesNotExist)?; | ||
|
||
proposal.status = ProposalStatus::Accepted; | ||
Proposals::<T>::insert((&client, &package_id, &carrier), proposal); | ||
|
||
Self::deposit_event(Event::<T>::ProposalAccepted { | ||
client: client.clone(), | ||
package: package_id.clone(), | ||
carrier: carrier.clone(), | ||
}); | ||
|
||
Ok(()) | ||
} | ||
|
||
pub fn calculate_final_fee_amount( | ||
client: &T::AccountId, | ||
package_id: &PackageId, | ||
carrier: &T::AccountId, | ||
collected_on: BlockNumberFor<T>, | ||
delivered_on: BlockNumberFor<T>, | ||
) -> Result<u128, frame_support::dispatch::DispatchError> { | ||
let proposal = Proposals::<T>::get((&client, &package_id, &carrier)) | ||
.ok_or(Error::<T>::ProposalDoesNotExist)?; | ||
|
||
// For every penalty period passed, deduct the penalty amount from the final fee | ||
let blocks_taken = delivered_on - collected_on; | ||
let periods = blocks_taken / proposal.penalty_period.into(); | ||
let penalty: u128 = periods.saturated_into::<u128>() * proposal.penalty_amount; | ||
|
||
let final_fee = if proposal.maximum_fee_amount - penalty > proposal.minimum_fee_amount { | ||
proposal.maximum_fee_amount - penalty | ||
} else { | ||
proposal.minimum_fee_amount | ||
}; | ||
|
||
Ok(final_fee) | ||
} | ||
|
||
pub fn remove_concluded_proposals(prefix: (T::AccountId, PackageId)) { | ||
let _ = Proposals::<T>::clear_prefix(prefix, u32::MAX, None); | ||
} | ||
} |
Oops, something went wrong.