Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: retire purchased credits if payment is stripe #246

Merged
merged 10 commits into from
Sep 18, 2023
31 changes: 21 additions & 10 deletions pallets/carbon-credits/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ use sp_std::{cmp, convert::TryInto, vec::Vec};

use crate::{
AssetIdLookup, AuthorizedAccounts, BatchGroupOf, BatchRetireDataList, BatchRetireDataOf,
Config, Error, Event, NextAssetId, NextItemId, NextProjectId, Pallet, ProjectCreateParams,
ProjectDetail, Projects, RetiredCarbonCreditsData, RetiredCredits, ShortStringOf,
Config, Error, Event, NextAssetId, NextItemId, NextProjectId, Pallet, ProjectApprovalStatus,
ProjectCreateParams, ProjectDetail, Projects, RetiredCarbonCreditsData, RetiredCredits,
ShortStringOf,
};

impl<T: Config> Pallet<T> {
Expand Down Expand Up @@ -61,10 +62,15 @@ impl<T: Config> Pallet<T> {
// ensure the project exists
let project = project.as_mut().ok_or(Error::<T>::ProjectNotFound)?;

project.approved = is_approved;
ensure!(
project.approved == ProjectApprovalStatus::Pending,
Error::<T>::ApprovalAlreadyProcessed
);

// if approved, create assets
if is_approved {
project.approved = ProjectApprovalStatus::Approved;

let mut created_asset_ids: Vec<T::AssetId> = Default::default();

for (group_id, mut group) in project.batch_groups.iter_mut() {
Expand Down Expand Up @@ -99,6 +105,7 @@ impl<T: Config> Pallet<T> {
asset_ids: created_asset_ids,
});
} else {
project.approved = ProjectApprovalStatus::Rejected;
Self::deposit_event(Event::ProjectRejected { project_id });
}

Expand Down Expand Up @@ -195,7 +202,7 @@ impl<T: Config> Pallet<T> {
royalties: params.royalties,
created: now,
updated: None,
approved: false,
approved: ProjectApprovalStatus::Pending,
};

*project = Some(new_project);
Expand All @@ -216,7 +223,7 @@ impl<T: Config> Pallet<T> {
let project = project.as_mut().ok_or(Error::<T>::ProjectNotFound)?;

// approved projects cannot be modified
ensure!(!project.approved, Error::<T>::CannotModifyApprovedProject);
ensure!(!project.approved.is_approved(), Error::<T>::CannotModifyApprovedProject);

// only originator can resubmit
ensure!(project.originator == admin, Error::<T>::NotAuthorised);
Expand Down Expand Up @@ -279,7 +286,7 @@ impl<T: Config> Pallet<T> {
batch_groups: batch_group_map,
created: project.created,
updated: Some(now),
approved: false,
approved: ProjectApprovalStatus::Pending,
};

*project = new_project;
Expand All @@ -305,7 +312,7 @@ impl<T: Config> Pallet<T> {
let project = project.as_mut().ok_or(Error::<T>::ProjectNotFound)?;

// non approved project needs to be resubmitted
ensure!(project.approved, Error::<T>::CannotUpdateUnapprovedProject);
ensure!(project.approved.is_approved(), Error::<T>::CannotUpdateUnapprovedProject);

// only originator can resubmit
ensure!(project.originator == admin, Error::<T>::NotAuthorised);
Expand Down Expand Up @@ -347,7 +354,7 @@ impl<T: Config> Pallet<T> {
let project = project.as_mut().ok_or(Error::<T>::ProjectNotFound)?;

// non approved project needs to be resubmitted
ensure!(project.approved, Error::<T>::CannotUpdateUnapprovedProject);
ensure!(project.approved.is_approved(), Error::<T>::CannotUpdateUnapprovedProject);

// only originator can resubmit
ensure!(project.originator == admin, Error::<T>::NotAuthorised);
Expand Down Expand Up @@ -418,7 +425,7 @@ impl<T: Config> Pallet<T> {
let project = project.as_mut().ok_or(Error::<T>::ProjectNotFound)?;

// ensure the project is approved
ensure!(project.approved, Error::<T>::ProjectNotApproved);
ensure!(project.approved.is_approved(), Error::<T>::ProjectNotApproved);

// ensure the group exists
let mut group =
Expand Down Expand Up @@ -505,7 +512,7 @@ impl<T: Config> Pallet<T> {
let project = project.as_mut().ok_or(Error::<T>::ProjectNotFound)?;

// ensure the project is approved
ensure!(project.approved, Error::<T>::ProjectNotApproved);
ensure!(project.approved.is_approved(), Error::<T>::ProjectNotApproved);

// ensure the group exists
let mut group =
Expand Down Expand Up @@ -535,6 +542,10 @@ impl<T: Config> Pallet<T> {

let actual = cmp::min(available_to_retire, remaining);

if actual.is_zero() {
continue
}

batch.retired = batch.retired.checked_add(&actual).ok_or(Error::<T>::Overflow)?;

// create data of retired batch
Expand Down
19 changes: 15 additions & 4 deletions pallets/carbon-credits/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ pub mod migration;
pub use functions::*;

mod weights;
use frame_support::traits::Contains;
use frame_support::{pallet_prelude::DispatchResult, traits::Contains};
pub use weights::WeightInfo;

#[frame_support::pallet]
Expand Down Expand Up @@ -356,6 +356,8 @@ pub mod pallet {
GroupNotFound,
/// Can only update an approved project, use resubmit for rejected projects
CannotUpdateUnapprovedProject,
/// The project approval status has been processed
ApprovalAlreadyProcessed,
}

#[pallet::call]
Expand Down Expand Up @@ -479,7 +481,7 @@ pub mod pallet {
T::ForceOrigin::ensure_origin(origin)?;
// remove the account_id from the list of authorized accounts if already exists
AuthorizedAccounts::<T>::try_mutate(|account_list| -> DispatchResult {
if let Ok(index) = account_list.binary_search(&account_id) {
if let Some(index) = account_list.iter().position(|a| a == &account_id) {
account_list.swap_remove(index);
Self::deposit_event(Event::AuthorizedAccountRemoved { account_id });
}
Expand Down Expand Up @@ -623,12 +625,21 @@ pub mod pallet {
/// Struct to verify if a given asset_id is representing a carbon credit project
impl<T: Config> primitives::CarbonCreditsValidator for Pallet<T> {
type ProjectId = T::ProjectId;

type Address = T::AccountId;
type GroupId = T::GroupId;

type AssetId = T::AssetId;
type Amount = T::Balance;

fn get_project_details(asset_id: &Self::AssetId) -> Option<(Self::ProjectId, Self::GroupId)> {
AssetIdLookup::<T>::get(asset_id)
}

fn retire_credits(
sender: Self::Address,
project_id: Self::ProjectId,
group_id: Self::GroupId,
amount: Self::Amount,
) -> DispatchResult {
Self::retire_carbon_credits(sender, project_id, group_id, amount, Default::default())
}
}
80 changes: 57 additions & 23 deletions pallets/carbon-credits/src/migration.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use super::*;
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::BoundedVec;
use scale_info::TypeInfo;

pub mod v2 {
pub mod v3 {
use super::*;
use crate::types::ProjectDetail;

Expand All @@ -12,26 +11,39 @@ pub mod v2 {
traits::{Get, OnRuntimeUpgrade},
};

pub struct MigrateToV2<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for MigrateToV2<T> {
pub struct MigrateToV3<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for MigrateToV3<T> {
fn on_runtime_upgrade() -> Weight {
log::info!("V2 MIGRATION : About to execute carbon-credits migration!");
log::info!("V3 MIGRATION : About to execute carbon-credits migration!");

// convert the project type to new format
RetiredCredits::<T>::translate::<OldRetiredCarbonCreditsData<T>, _>(
|_asset_id, _item_id, old| -> Option<RetiredCarbonCreditsData<T>> {
let converted_data = RetiredCarbonCreditsData {
account: old.account,
retire_data: old.retire_data,
timestamp: old.timestamp,
count: old.count,
reason: Default::default(), // new value
Projects::<T>::translate::<OldProjectDetail<T>, _>(
|_project_id, old| -> Option<ProjectDetail<T>> {
let converted_data = ProjectDetail {
originator: old.originator,
name: old.name,
description: old.description,
location: old.location,
images: old.images,
videos: old.videos,
documents: old.documents,
registry_details: old.registry_details,
sdg_details: old.sdg_details,
royalties: old.royalties,
batch_groups: old.batch_groups,
created: old.created,
updated: old.updated,
approved: match old.approved {
// modified field
true => ProjectApprovalStatus::Approved,
false => ProjectApprovalStatus::Rejected,
},
};
Some(converted_data)
},
);

log::info!("MIGRATION : Carbon credits migration complete!");
log::info!("V3 MIGRATION : Carbon credits migration complete!");

T::DbWeight::get().reads_writes(1, 1)
}
Expand All @@ -51,13 +63,35 @@ pub mod v2 {
#[codec(mel_bound(T: pallet::Config))]
#[derive(frame_support::DebugNoBound)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct OldRetiredCarbonCreditsData<T: pallet::Config> {
/// The AccountId that retired the credits
pub account: T::AccountId,
/// The details of the batches the tokens were retired from
pub retire_data: BatchRetireDataList<T>,
/// The 'BlockNumber' of retirement
pub timestamp: T::BlockNumber,
/// The total count of credits retired
pub count: T::Balance,
pub struct OldProjectDetail<T: pallet::Config> {
/// The originator of the project
pub originator: T::AccountId,
/// Name of the project
pub name: ShortStringOf<T>,
/// Description of the project
pub description: LongStringOf<T>,
/// Location co-ordinates of thie project
pub location: LongStringOf<T>,
/// List of ipfs-hashes of images related to the project
pub images: IpfsLinkListsOf<T>,
/// List of ipfs-hashes of videos related to the project
pub videos: IpfsLinkListsOf<T>,
/// List of ipfs-hashes of documents related to the project
pub documents: IpfsLinkListsOf<T>,
/// Details of the project as represented in registry
pub registry_details: RegistryListOf<T>,
/// SDG details
pub sdg_details: SDGTypesListOf<T>,
/// The royalties to be paid when tokens are purchased
pub royalties: Option<RoyaltyRecipientsOf<T>>,
/// groups included in the project
pub batch_groups: BatchGroupMapOf<T>,
// origination details
/// Creation time of project
pub created: T::BlockNumber,
/// Last updation time of project
pub updated: Option<T::BlockNumber>,

/// approval status - a project can only mint tokens once approved
pub approved: bool,
}
22 changes: 11 additions & 11 deletions pallets/carbon-credits/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ fn create_works_for_single_batch() {
assert_eq!(stored_data.originator, originator_account);
assert_eq!(stored_data.name, creation_params.name);
assert_eq!(stored_data.registry_details, get_default_registry_details::<Test>());
assert!(!stored_data.approved);
assert!(!stored_data.approved.is_approved());

let group_data = stored_data.batch_groups.get(&0u32).unwrap();
assert_eq!(stored_data.sdg_details, get_default_sdg_details::<Test>());
Expand Down Expand Up @@ -332,7 +332,7 @@ fn create_works_for_multiple_batch() {
assert_eq!(stored_data.originator, originator_account);
assert_eq!(stored_data.name, creation_params.name);
assert_eq!(stored_data.registry_details, get_default_registry_details::<Test>());
assert!(!stored_data.approved);
assert!(!stored_data.approved.is_approved());

let group_data = stored_data.batch_groups.get(&0u32).unwrap();
assert_eq!(stored_data.sdg_details, get_default_sdg_details::<Test>());
Expand Down Expand Up @@ -479,7 +479,7 @@ fn resubmit_works() {
assert_eq!(stored_data.originator, originator_account);
assert_eq!(stored_data.name, creation_params.name);
assert_eq!(stored_data.registry_details, get_default_registry_details::<Test>());
assert!(!stored_data.approved);
assert!(!stored_data.approved.is_approved());

// the supply of both batches should be added correctly
let group_data = stored_data.batch_groups.get(&0u32).unwrap();
Expand Down Expand Up @@ -558,7 +558,7 @@ fn approve_project_works() {
let stored_data = Projects::<Test>::get(project_id).unwrap();

// sanity check
assert!(!stored_data.approved);
assert!(!stored_data.approved.is_approved());

// approve should work now
assert_ok!(CarbonCredits::approve_project(
Expand All @@ -569,7 +569,7 @@ fn approve_project_works() {

// ensure storage changed correctly
let stored_data = Projects::<Test>::get(project_id).unwrap();
assert!(stored_data.approved);
assert!(stored_data.approved.is_approved());
// the asset_id should be set correctly
let group_data = stored_data.batch_groups.get(&0u32).unwrap();
assert_eq!(group_data.asset_id, asset_id);
Expand Down Expand Up @@ -772,7 +772,7 @@ fn mint_without_list_to_marketplace_works_for_single_batch() {
assert_eq!(group_data.total_supply, 100_u32.into());
assert_eq!(group_data.minted, amount_to_mint);
assert_eq!(group_data.retired, 0_u32.into());
assert!(stored_data.approved);
assert!(stored_data.approved.is_approved());

// the batch should also be updated with minted count
let batch_detail = group_data.batches.first().unwrap();
Expand Down Expand Up @@ -865,7 +865,7 @@ fn mint_without_list_to_marketplace_works_for_multiple_batches() {
assert_eq!(group_data.total_supply, 200_u32.into());
assert_eq!(group_data.minted, amount_to_mint);
assert_eq!(group_data.retired, 0_u32.into());
assert!(stored_data.approved);
assert!(stored_data.approved.is_approved());

// the batch should also be updated with minted count
// we have a total supply of 200, with 100 in each batch
Expand Down Expand Up @@ -948,7 +948,7 @@ fn mint_without_list_to_marketplace_works_for_multiple_batches() {
assert_eq!(group_data.total_supply, 200_u32.into());
assert_eq!(group_data.minted, 200_u32.into());
assert_eq!(group_data.retired, 0_u32.into());
assert!(stored_data.approved);
assert!(stored_data.approved.is_approved());

// the batch should also be updated with minted count
// we have a total supply of 200, with 100 in each batch
Expand Down Expand Up @@ -1440,7 +1440,7 @@ fn force_approve_and_mint_credits_works() {
assert_eq!(group_data.total_supply, 100_u32.into());
assert_eq!(group_data.minted, amount_to_mint);
assert_eq!(group_data.retired, 0_u32.into());
assert!(stored_data.approved);
assert!(stored_data.approved.is_approved());
});
}

Expand Down Expand Up @@ -1516,7 +1516,7 @@ fn update_works() {
assert_eq!(stored_data.originator, originator_account);
assert_eq!(stored_data.name, creation_params.name);
assert_eq!(stored_data.registry_details, get_default_registry_details::<Test>());
assert!(stored_data.approved);
assert!(stored_data.approved.is_approved());

// the batch group should not be updated
let group_data = stored_data.batch_groups.get(&0u32).unwrap();
Expand Down Expand Up @@ -1551,7 +1551,7 @@ fn add_batch_group_works() {
assert_eq!(stored_data.originator, originator_account);
assert_eq!(stored_data.name, creation_params.name);
assert_eq!(stored_data.registry_details, get_default_registry_details::<Test>());
assert!(!stored_data.approved);
assert!(!stored_data.approved.is_approved());

let group_data = stored_data.batch_groups.get(&0u32).unwrap();
assert_eq!(stored_data.sdg_details, get_default_sdg_details::<Test>());
Expand Down
Loading
Loading