Skip to content

Commit

Permalink
Handling a community-governed treasury account (#300)
Browse files Browse the repository at this point in the history
* chore(communities:tests): remove qualified references to sp_runtime::DispatchError

* refactor(communities:functions): simplify qualified references to types and traits

* feat(pallets/communities): implement assets_transfer

* feat(pallets/communities): implement balance_transfer

* chore(pallets/communities): update docs

* fix(communities:functions/tokens): apply fmt

* refactor(communities:functions/tokens): rename mod to treasury

* refactor(communities:functions): move do_create_community_account to treasury module

* chore(communities:functions/registration): rename as registry

* m:change(communities:registry): directly override value on do_set_metadata
  • Loading branch information
pandres95 authored Sep 28, 2023
1 parent 6831e76 commit cc217c5
Show file tree
Hide file tree
Showing 12 changed files with 402 additions and 109 deletions.
7 changes: 3 additions & 4 deletions pallets/communities/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,8 @@ facilitating its participants to have governance over the community entity
- **Community:** An entity comprised of _members_ —each one defined by their
[`AccountId`][1]— with a given _description_ who can vote on _proposals_
and actively take decisions on behalf of it. Communities are given a
_treasury account_ and can issue _governance_ and _economic_ tokens. It is
required that a community contributes to the network to be active and
operate within it.
_treasury account_ and can issue tokens. It is required that a community
contributes to the network to be active and operate within it.
- **Community Description:** A set of metadata used to identify a community
distinctively. Typically, a name, a list of locations (given as a list of
one or more [`H3Index`][2]), and a list of URL links.
Expand Down Expand Up @@ -176,5 +175,5 @@ dispatched through an approved proposal. !
[6]: https://github.com/virto-network/virto-node/pull/282
[7]: https://paritytech.github.io/substrate/master/pallet_assets/index.html#terminology
[8]: https://docs.substrate.io/reference/glossary/#existential-deposit
[t00]: src/lib.rs#L233
[t00]: src/lib.rs#L237
[t01]: src/types.rs#L57
4 changes: 2 additions & 2 deletions pallets/communities/src/functions/challenges.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::*;

impl<T: Config> Pallet<T> {
pub(crate) fn ensure_active(community_id: &T::CommunityId) -> DispatchResult {
pub(crate) fn ensure_active(community_id: &CommunityIdOf<T>) -> DispatchResult {
let community_info =
<CommunityInfo<T>>::try_get(community_id).map_err(|_| Error::<T>::CommunityDoesNotExist)?;

Expand All @@ -13,7 +13,7 @@ impl<T: Config> Pallet<T> {
}

#[allow(dead_code)]
pub(crate) fn do_force_complete_challenge(community_id: &T::CommunityId) -> DispatchResult {
pub(crate) fn do_force_complete_challenge(community_id: &CommunityIdOf<T>) -> DispatchResult {
<CommunityInfo<T>>::try_mutate_exists(community_id, |value| {
let Some(community_info) = value else {
return Err::<(), DispatchError>(Error::<T>::CommunityDoesNotExist.into());
Expand Down
4 changes: 2 additions & 2 deletions pallets/communities/src/functions/getters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ use super::*;
use frame_support::sp_runtime::traits::AccountIdConversion;

impl<T: Config> Pallet<T> {
pub(crate) fn get_community_account_id(community_id: &T::CommunityId) -> T::AccountId {
pub(crate) fn get_community_account_id(community_id: &CommunityIdOf<T>) -> AccountIdOf<T> {
T::PalletId::get().into_sub_account_truncating(community_id)
}

pub(crate) fn get_community_admin(community_id: &T::CommunityId) -> Result<T::AccountId, DispatchError> {
pub(crate) fn get_community_admin(community_id: &CommunityIdOf<T>) -> Result<AccountIdOf<T>, DispatchError> {
let Some(community) = <CommunityInfo<T>>::get(community_id) else {
Err(Error::<T>::CommunityDoesNotExist)?
};
Expand Down
14 changes: 7 additions & 7 deletions pallets/communities/src/functions/membership.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ use super::*;
impl<T: Config> Pallet<T> {
pub(crate) fn ensure_origin_member(
origin: OriginFor<T>,
community_id: &T::CommunityId,
community_id: &CommunityIdOf<T>,
) -> Result<(), DispatchError> {
let caller = ensure_signed(origin)?;

if !<CommunityMembers<T>>::contains_key(community_id, caller) {
if Self::member_information(community_id, caller).is_none() {
return Err(DispatchError::BadOrigin);
}

Expand All @@ -16,7 +16,7 @@ impl<T: Config> Pallet<T> {

pub(crate) fn ensure_origin_privileged(
origin: OriginFor<T>,
community_id: &T::CommunityId,
community_id: &CommunityIdOf<T>,
) -> Result<(), DispatchError> {
if let Some(caller) = ensure_signed_or_root(origin)? {
if caller != Self::get_community_admin(community_id)? {
Expand All @@ -27,7 +27,7 @@ impl<T: Config> Pallet<T> {
Ok(())
}

pub(crate) fn do_insert_member(community_id: &T::CommunityId, who: &T::AccountId) -> DispatchResult {
pub(crate) fn do_insert_member(community_id: &CommunityIdOf<T>, who: &AccountIdOf<T>) -> DispatchResult {
<CommunityMembers<T>>::try_mutate_exists(community_id, who, |value| {
if value.is_some() {
return Err(Error::<T>::AlreadyAMember.into());
Expand All @@ -37,7 +37,7 @@ impl<T: Config> Pallet<T> {
*value = Some(Default::default());

// Increases member count
let members_count = <CommunityMembersCount<T>>::try_get(community_id).unwrap_or_default();
let members_count = Self::members_count(community_id).unwrap_or_default();
<CommunityMembersCount<T>>::set(community_id, members_count.checked_add(1));

Ok(())
Expand All @@ -50,7 +50,7 @@ impl<T: Config> Pallet<T> {
return Err(Error::<T>::NotAMember.into());
}

let Some(community_info) = <CommunityInfo<T>>::get(community_id) else {
let Some(community_info) = Self::community(community_id) else {
return Err(Error::<T>::CommunityDoesNotExist.into());
};

Expand All @@ -62,7 +62,7 @@ impl<T: Config> Pallet<T> {
*value = None;

// Decreases member count
let members_count = <CommunityMembersCount<T>>::try_get(community_id).unwrap_or_default();
let members_count = Self::members_count(community_id).unwrap_or_default();
<CommunityMembersCount<T>>::set(community_id, members_count.checked_sub(1));

Ok(())
Expand Down
7 changes: 6 additions & 1 deletion pallets/communities/src/functions/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
pub(self) use crate::*;
pub(self) use frame_support::pallet_prelude::*;
pub(self) use frame_support::traits::tokens::{
fungible::{Inspect, Mutate, MutateFreeze},
fungibles::Mutate as MutateFuns,
};
pub(self) use frame_system::pallet_prelude::*;
pub(self) use types::*;

mod challenges;
mod getters;
mod membership;
mod registration;
mod registry;
mod treasury;
77 changes: 0 additions & 77 deletions pallets/communities/src/functions/registration.rs

This file was deleted.

41 changes: 41 additions & 0 deletions pallets/communities/src/functions/registry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use super::*;

impl<T: Config> Pallet<T> {
pub(crate) fn community_exists(community_id: &CommunityIdOf<T>) -> bool {
Self::community(community_id).is_some()
}

/// Stores an initial info about the community
/// Sets the caller as the community admin, the initial community state
/// to its default value(awaiting)
pub(crate) fn do_register_community(who: &AccountIdOf<T>, community_id: &CommunityIdOf<T>) -> DispatchResult {
// Check that the community doesn't exist
if Self::community_exists(community_id) {
return Err(Error::<T>::CommunityAlreadyExists.into());
}

<CommunityInfo<T>>::insert(
community_id.clone(),
Community {
admin: who.clone(),
state: Default::default(),
sufficient_asset_id: None,
},
);

Self::do_insert_member(community_id, who)?;

Ok(())
}

pub(crate) fn do_set_metadata(
community_id: &CommunityIdOf<T>,
value: types::CommunityMetadata<T>,
) -> DispatchResult {
<pallet::CommunityMetadata<T>>::try_mutate(community_id, |metadata| {
*metadata = Some(value);

Ok(())
})
}
}
48 changes: 48 additions & 0 deletions pallets/communities/src/functions/treasury.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use super::*;
use frame_support::traits::tokens::Preservation;

impl<T: Config> Pallet<T> {
/// Takes a deposit from the caller and
pub(crate) fn do_create_community_account(
caller: &AccountIdOf<T>,
community_id: &CommunityIdOf<T>,
) -> DispatchResult {
let community_account_id = Self::get_community_account_id(community_id);
let minimum_balance = T::Balances::minimum_balance();

T::Balances::transfer(
caller,
&community_account_id,
minimum_balance,
frame_support::traits::tokens::Preservation::Preserve,
)?;

// Lock funds so the account can exist at all times
T::Balances::set_freeze(&T::FreezeIdentifier::get(), &community_account_id, minimum_balance)?;

Ok(())
}

pub(crate) fn do_assets_transfer(
community_id: &CommunityIdOf<T>,
asset_id: AssetIdOf<T>,
dest: &AccountIdOf<T>,
amount: BalanceOf<T>,
) -> DispatchResult {
let community_account_id = Self::get_community_account_id(community_id);
T::Assets::transfer(asset_id, &community_account_id, dest, amount, Preservation::Preserve)?;

Ok(())
}

pub(crate) fn do_balance_transfer(
community_id: &CommunityIdOf<T>,
dest: &AccountIdOf<T>,
amount: NativeBalanceOf<T>,
) -> DispatchResult {
let community_account_id = Self::get_community_account_id(community_id);
T::Balances::transfer(&community_account_id, dest, amount, Preservation::Preserve)?;

Ok(())
}
}
58 changes: 52 additions & 6 deletions pallets/communities/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,8 @@
//! - **Community:** An entity comprised of _members_ —each one defined by their
//! [`AccountId`][1]— with a given _description_ who can vote on _proposals_
//! and actively take decisions on behalf of it. Communities are given a
//! _treasury account_ and can issue _governance_ and _economic_ tokens. It is
//! required that a community contributes to the network to be active and
//! operate within it.
//! _treasury account_ and can issue tokens. It is required that a community
//! contributes to the network to be active and operate within it.
//! - **Community Description:** A set of metadata used to identify a community
//! distinctively. Typically, a name, a list of locations (given as a list of
//! one or more [`H3Index`][2]), and a list of URL links.
Expand Down Expand Up @@ -144,9 +143,9 @@
//! _"free"_," further ones would be subject to network-wide referenda.
//! - `close_proposal`: Forcefully closes a proposal, dispatching the call when
//! approved.
//! - `assets_transfer`: Transfers an amount of a given asset from the treasury
//! account to a beneficiary.
//! - `balance_transfer`: Transfers funds from the treasury account to a
//! - [`assets_transfer`][c04]: Transfers an amount of a given asset from the
//! treasury account to a beneficiary.
//! - [`balance_transfer`][c05]: Transfers funds from the treasury account to a
//! beneficiary.
//! - `set_sufficient_asset`: Marks an [asset][7] issued by the community as
//! sufficient. Only one asset at a time can be marked as such.
Expand Down Expand Up @@ -191,6 +190,8 @@
//! [c01]: `crate::Pallet::set_metadata`
//! [c02]: `crate::Pallet::add_member`
//! [c03]: `crate::Pallet::remove_member`
//! [c04]: `crate::Pallet::assets_transfer`
//! [c05]: `crate::Pallet::balance_transfer`
//!
//! [g00]: `crate::Pallet::community`
//! [g01]: `crate::Pallet::metadata`
Expand Down Expand Up @@ -462,5 +463,50 @@ pub mod pallet {

Ok(())
}

/// Transfers an amount of a given asset from the treasury account to a
/// beneficiary.
#[pallet::call_index(4)]
#[pallet::weight(T::WeightInfo::assets_transfer())]
pub fn assets_transfer(
origin: OriginFor<T>,
community_id: T::CommunityId,
asset_id: AssetIdOf<T>,
dest: AccountIdLookupOf<T>,
amount: BalanceOf<T>,
) -> DispatchResult {
Self::ensure_origin_privileged(origin, &community_id)?;
Self::ensure_active(&community_id)?;

Self::do_assets_transfer(
&community_id,
asset_id,
&<<T as frame_system::Config>::Lookup as StaticLookup>::lookup(dest)?,
amount,
)?;

Ok(())
}

/// Transfers funds from the treasury account to a beneficiary
#[pallet::call_index(5)]
#[pallet::weight(T::WeightInfo::balance_transfer())]
pub fn balance_transfer(
origin: OriginFor<T>,
community_id: T::CommunityId,
dest: AccountIdLookupOf<T>,
amount: NativeBalanceOf<T>,
) -> DispatchResult {
Self::ensure_origin_privileged(origin, &community_id)?;
Self::ensure_active(&community_id)?;

Self::do_balance_transfer(
&community_id,
&<<T as frame_system::Config>::Lookup as StaticLookup>::lookup(dest)?,
amount,
)?;

Ok(())
}
}
}
Loading

0 comments on commit cc217c5

Please sign in to comment.