Skip to content

Commit

Permalink
Add initial logistics app chain functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronbassett committed Nov 2, 2023
1 parent c5945c1 commit 791ab86
Show file tree
Hide file tree
Showing 26 changed files with 905 additions and 132 deletions.
34 changes: 34 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 43 additions & 0 deletions pallets/carrier/Cargo.toml
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"]
1 change: 1 addition & 0 deletions pallets/carrier/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
License: MIT-0
93 changes: 93 additions & 0 deletions pallets/carrier/src/calls.rs
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(())
}
}
}
11 changes: 11 additions & 0 deletions pallets/carrier/src/config.rs
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>;
}
}
14 changes: 14 additions & 0 deletions pallets/carrier/src/errors.rs
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,
}
}
21 changes: 21 additions & 0 deletions pallets/carrier/src/events.rs
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 },
}
}
126 changes: 126 additions & 0 deletions pallets/carrier/src/lib.rs
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);
}
}
Loading

0 comments on commit 791ab86

Please sign in to comment.