Skip to content

Add pallet Crowdfunding #149

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

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

7 changes: 5 additions & 2 deletions pallets/crowdfunding/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pallet-identity = { git = "https://github.com/paritytech/substrate", branch = "p
orml-traits = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v0.9.39", default-features = false }

common-types = { path = "../../libs/common-types", default-features = false }
common-runtime = { path = "../../runtime/common", default-features = false}
pallet-proposals = { path= "../proposals", default-features = false}
pallet-deposits = {path= "../deposits", default-features = false }

Expand All @@ -36,6 +37,7 @@ sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkad
sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39"}
pallet-proposals = { path= "../proposals"}
common-types = { path = "../../libs/common-types"}
common-runtime = { path = "../../runtime/common"}
sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39"}
pallet-identity = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39"}
orml-tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v0.9.39" }
Expand All @@ -57,8 +59,9 @@ std = [
"pallet-proposals/std",
"sp-core/std",
"pallet-identity/std",
"pallet-xcm/std"
"pallet-xcm/std",
"common-runtime/std",
]

runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks", "pallet-xcm/runtime-benchmarks"]
runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "common-runtime/runtime-benchmarks"]
try-runtime = ["frame-support/try-runtime"]
49 changes: 24 additions & 25 deletions pallets/crowdfunding/src/benchmarking.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
//! Benchmarking setup for pallet-template
//! Benchmarking setup for pallet-crowdfunding

use super::*;

use crate::Pallet as CrowdFunding;
use common_types::CurrencyId;
use frame_benchmarking::v1::{account, benchmarks, impl_benchmark_test_suite, whitelisted_caller};
use frame_support::assert_ok;
use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, whitelisted_caller};
use frame_support::{assert_ok, sp_runtime::SaturatedConversion};
use frame_system::{EventRecord, RawOrigin};
use orml_traits::MultiCurrency;
use pallet_proposals::ProposedMilestone;
use sp_arithmetic::per_things::Percent;
use sp_core::{Get, H256};
use sp_std::collections::btree_map::BTreeMap;
use sp_std::{vec::Vec, collections::btree_map::BTreeMap};

benchmarks! {
where_clause {
where T::AccountId: AsRef<[u8]>,
}

create_crowdfund {
let caller: T::AccountId = whitelisted_caller();
let caller: T::AccountId = create_funded_user::<T>("initiator", 1, 100_000_000_000_000_000u128);
let milestones = get_max_milestones::<T>();
let required_funds = u32::MAX;
let currency_id = CurrencyId::Native;
let agg_hash = H256::from([10u8; 32]);
let crowdfund_key = 0;
// (Origin, agg_hash, ProposedMilestones, RequiredFunds, CurrencyId)
}: _(RawOrigin::Signed(whitelisted_caller()), agg_hash, milestones, required_funds.into(), CurrencyId::Native)
}: _(RawOrigin::Signed(caller.clone()), agg_hash, milestones, required_funds.into(), CurrencyId::Native)
verify {
assert_last_event::<T>(Event::<T>::CrowdFundCreated(caller, agg_hash, crowdfund_key, required_funds.into(), CurrencyId::Native).into());
}
Expand Down Expand Up @@ -85,7 +84,7 @@ benchmarks! {
contribute {
let required_funds = u32::MAX;
create_crowdfund_common::<T>(required_funds);
let alice: T::AccountId = create_funded_user::<T>("candidate", 1, 100_000);
let alice: T::AccountId = create_funded_user::<T>("candidate", 1, 100_000_000_000_000_000u128);
let caller: T::AccountId = whitelisted_caller();
let _ = CrowdFunding::<T>::open_contributions(RawOrigin::Root.into(), 0);

Expand All @@ -96,11 +95,11 @@ benchmarks! {
}

approve_crowdfund_for_milestone_submission {
let required_funds: u32 = 100_000u32;
let required_funds: u32 = u32::MAX;
create_crowdfund_common::<T>(required_funds);
let alice: T::AccountId = create_funded_user::<T>("candidate", 1, required_funds.into());
let _ = CrowdFunding::<T>::open_contributions(RawOrigin::Root.into(), 0);
let _ = CrowdFunding::<T>::contribute(RawOrigin::Signed(alice.clone()).into(), 0u32, required_funds.into());
let alice: T::AccountId = create_funded_user::<T>("candidate", 1, 100_000_000_000_000_000u128);
assert_ok!(CrowdFunding::<T>::open_contributions(RawOrigin::Root.into(), 0));
assert_ok!(CrowdFunding::<T>::contribute(RawOrigin::Signed(alice.clone()).into(), 0u32, required_funds.into()));

//(Origin, CrowdFundKey)
}: _(RawOrigin::Root, 0)
Expand All @@ -109,25 +108,23 @@ benchmarks! {
}
}

impl_benchmark_test_suite!(CrowdFunding, crate::mock::new_test_ext(), crate::mock::Test);

fn create_funded_user<T: Config>(
string: &'static str,
pub fn create_funded_user<T: Config>(
seed: &'static str,
n: u32,
balance_factor: u32,
balance_factor: u128,
) -> T::AccountId {
let user = account(string, n, 99);
let balance: BalanceOf<T> = balance_factor.into();
let _ = <T::MultiCurrency as MultiCurrency<<T as frame_system::Config>::AccountId>>::deposit(
CurrencyId::Native,
&user,
balance,
);
let user = account(seed, n, 0);
assert_ok!(<T::MultiCurrency as MultiCurrency<
<T as frame_system::Config>::AccountId,
>>::deposit(
CurrencyId::Native, &user, balance_factor.saturated_into()
));
user
}


fn create_crowdfund_common<T: Config>(required_funds: u32) -> T::AccountId {
let bob: T::AccountId = create_funded_user::<T>("initiator", 1, 100_000_000);
let bob: T::AccountId = create_funded_user::<T>("initiator", 1, 100_000_000_000_000_000u128);
let milestones = get_max_milestones::<T>();

let agg_hash = H256::from([20; 32]);
Expand Down Expand Up @@ -174,3 +171,5 @@ where
let EventRecord { event, .. } = &events[events.len() - 1];
assert_eq!(event, &system_event);
}

impl_benchmark_test_suite!(CrowdFunding, crate::mock::new_test_ext(), crate::mock::Test);
77 changes: 45 additions & 32 deletions pallets/crowdfunding/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![cfg_attr(not(feature = "std"), no_std)]
pub use pallet::*;

mod weights;
pub mod weights;

#[cfg(test)]
mod mock;
Expand All @@ -26,6 +26,7 @@ pub mod pallet {
use sp_arithmetic::per_things::Percent;
use sp_core::H256;
use sp_std::collections::btree_map::BTreeMap;
use pallet_deposits::traits::DepositHandler;

pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
pub type BalanceOf<T> =
Expand All @@ -46,6 +47,9 @@ pub mod pallet {
pub type BoundedProposedMilestones<T> =
BoundedVec<ProposedMilestone, <T as Config>::MaxMilestonesPerCrowdFund>;

type StorageItemOf<T> = <<T as Config>::DepositHandler as DepositHandler<BalanceOf<T>, AccountIdOf<T>>>::StorageItem;
type DepositIdOf<T> = <<T as Config>::DepositHandler as DepositHandler<BalanceOf<T>, AccountIdOf<T>>>::DepositId;

pub type CrowdFundKey = u32;
pub type MilestoneKey = u32;

Expand All @@ -62,14 +66,26 @@ pub mod pallet {
pub trait Config: frame_system::Config + pallet_identity::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type MultiCurrency: MultiReservableCurrency<AccountIdOf<Self>, CurrencyId = CurrencyId>;
/// The length of a round in the system.
type RoundExpiry: Get<BlockNumberFor<Self>>;
/// The maximum number of crowdfund keys in a given round.
type MaxKeysPerRound: Get<u32>;
/// The maximum number of contributors possible in a crowdfund.
type MaxContributionsPerCrowdFund: Get<u32>;
/// The maximum number of milestones that is possible in a crowdfund.
type MaxMilestonesPerCrowdFund: Get<u32>;
/// The maximum number of whitelist spots in a crowdfund.
type MaxWhitelistPerCrowdFund: Get<u32>;
/// Define whether a decent identity is required when creating a crowdfund.
type IsIdentityRequired: Get<bool>;
/// The authority responsible for governance actions.
type AuthorityOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// The type that converts a crowdfund into a project and allows milestone submission.
type IntoProposals: IntoProposal<AccountIdOf<Self>, BalanceOf<Self>, BlockNumberFor<Self>>;
/// The type responsible for handling storage deposits.
type DepositHandler: DepositHandler<BalanceOf<Self>, AccountIdOf<Self>>;
/// The type that the deposit fee will be calculated from.
type CrowdFundStorageItem: Get<StorageItemOf<Self>>;
type WeightInfo: WeightInfo;
}

Expand All @@ -79,13 +95,13 @@ pub mod pallet {

/// Stores a list of crowdfunds.
#[pallet::storage]
pub type CrowdFunds<T> = StorageMap<_, Blake2_128, CrowdFundKey, CrowdFund<T>, OptionQuery>;
pub type CrowdFunds<T> = StorageMap<_, Blake2_128Concat, CrowdFundKey, CrowdFund<T>, OptionQuery>;

/// Stores the crowdfund keys that are expiring on a given block.
/// Handled in the hooks,
#[pallet::storage]
pub type RoundsExpiring<T> =
StorageMap<_, Blake2_128, BlockNumberFor<T>, BoundedKeysPerRound<T>, ValueQuery>;
StorageMap<_, Blake2_128Concat, BlockNumberFor<T>, BoundedKeysPerRound<T>, ValueQuery>;

/// Tracks wether CrowdFunds are in a given round type.
/// Key 1 : CrowdFundID
Expand All @@ -94,15 +110,17 @@ pub mod pallet {
#[pallet::storage]
pub type CrowdFundsInRound<T> = StorageDoubleMap<
_,
Blake2_128,
Blake2_128Concat,
CrowdFundKey,
Blake2_128,
Blake2_128Concat,
RoundType,
BlockNumberFor<T>,
ValueQuery,
>;

/// Tracks the whitelists of a given crowdfund.
/// MIGRATION FROM PROPOSALS REFACTOR?
/// PALLET PREFIX HAS CHANGED
#[pallet::storage]
#[pallet::getter(fn whitelist_spots)]
pub type WhitelistSpots<T: Config> =
Expand Down Expand Up @@ -167,12 +185,14 @@ pub mod pallet {
IdentityNeeded,
/// Below the minimum required funds.
BelowMinimumRequiredFunds,
/// The crowdfund as already been converted to milestones.
CrowdFundAlreadyConverted,
/// The crowdfund has already been cancelled.
CrowdFundCancelled,
/// The conversion to a Project has failed.
CrowdFundConversionFailedGeneric,
/// You are trying to add too many whitelist spots.
WhiteListSpotLimitReached,
/// There are too many rounds inserted this block, please wait for the next on (6s)
TooManyRoundsInBlock,
}

#[pallet::call]
Expand Down Expand Up @@ -217,17 +237,10 @@ pub mod pallet {
agreement_hash: Option<H256>,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
if <T as Config>::IsIdentityRequired::get() {
Self::ensure_identity_is_decent(&who)?;
}


let crowdfund =
CrowdFunds::<T>::get(crowdfund_key).ok_or(Error::<T>::CrowdFundDoesNotExist)?;
ensure!(crowdfund.initiator == who, Error::<T>::UserIsNotInitiator);
ensure!(
!crowdfund.is_converted,
Error::<T>::CrowdFundAlreadyConverted
);
ensure!(
!crowdfund.approved_for_funding,
Error::<T>::CrowdFundAlreadyApproved
Expand Down Expand Up @@ -259,16 +272,15 @@ pub mod pallet {
let who = ensure_signed(origin)?;
Self::ensure_initiator(who, crowdfund_key)?;
let crowdfund_whitelist_spots = WhitelistSpots::<T>::get(crowdfund_key).unwrap_or(
BTreeMap::new()
.try_into()
.expect("Empty BTree is always smaller than bound; qed"),
BoundedBTreeMap::new()
);

let mut unbounded = crowdfund_whitelist_spots.into_inner();
unbounded.extend(new_whitelist_spots);

let bounded: BoundedWhitelistSpots<T> =
unbounded.try_into().map_err(|_| Error::<T>::Overflow)?;
unbounded.try_into().map_err(|_| Error::<T>::WhiteListSpotLimitReached)?;

<WhitelistSpots<T>>::insert(crowdfund_key, bounded);
let now = <frame_system::Pallet<T>>::block_number();
Self::deposit_event(Event::WhitelistAdded(crowdfund_key, now));
Expand Down Expand Up @@ -385,6 +397,11 @@ pub mod pallet {
let crowdfund_key = CrowdFundCount::<T>::get();

// Todo: Take storage deposit>
let deposit_id = <T as Config>::DepositHandler::take_deposit(
who.clone(),
<T as Config>::CrowdFundStorageItem::get(),
CurrencyId::Native,
)?;

// For now we keep them as proposed milestones until the project is able to submit.
let crowdfund = CrowdFund {
Expand All @@ -400,7 +417,7 @@ pub mod pallet {
created_on: <frame_system::Pallet<T>>::block_number(),
approved_for_funding: false,
cancelled: false,
is_converted: false,
deposit_id
};

// Add crowdfund to list
Expand Down Expand Up @@ -459,7 +476,7 @@ pub mod pallet {
.saturating_add(<T as Config>::RoundExpiry::get());
RoundsExpiring::<T>::try_mutate(expiry_block, |list| -> DispatchResult {
list.try_push(crowdfund_key)
.map_err(|_| Error::<T>::Overflow)?;
.map_err(|_| Error::<T>::TooManyRoundsInBlock)?;
Ok(())
})?;
CrowdFundsInRound::<T>::insert(
Expand Down Expand Up @@ -552,19 +569,16 @@ pub mod pallet {
crowdfund.initiator,
crowdfund.milestones.into_inner(),
FundingType::Proposal,
)
.map_err(|_| Error::<T>::CrowdFundConversionFailedGeneric)?;
)?;

<T as Config>::DepositHandler::return_deposit(crowdfund.deposit_id)?;
CrowdFunds::<T>::remove(crowdfund_key);

CrowdFunds::<T>::mutate(crowdfund_key, |crowdfund| {
if let Some(cf) = crowdfund {
cf.is_converted = true
}
Ok::<(), DispatchError>(())
})?;
Self::deposit_event(Event::CrowdFundApproved(crowdfund_key));
Ok(().into())
}

/// Actually calls storage. Could be improved.
pub fn ensure_initiator(
who: T::AccountId,
crowdfund_key: CrowdFundKey,
Expand All @@ -577,6 +591,7 @@ pub mod pallet {
}
}

/// Ensure the identity of an account is either Reasonable or KnownGood.
fn ensure_identity_is_decent(who: &T::AccountId) -> Result<(), Error<T>> {
let identity =
pallet_identity::Pallet::<T>::identity(who).ok_or(Error::<T>::IdentityNeeded)?;
Expand Down Expand Up @@ -605,10 +620,8 @@ pub mod pallet {
pub agreement_hash: H256,
pub initiator: AccountIdOf<T>,
pub created_on: BlockNumberFor<T>,
pub is_converted: bool,
pub deposit_id: DepositIdOf<T>,
}

// Called to ensure that an account is is a contributor to a crowdfund.
}

// Warning: This will allow the withdrawal of funds, approve is a governance action so should not be a problem.
Expand Down
Loading