paritytech / polkadot-sdk

The Parity Polkadot Blockchain SDK
https://polkadot.network/
1.86k stars 680 forks source link

Generalized Collective Extension Pallet(s) #90

Open joepetrowski opened 1 year ago

joepetrowski commented 1 year ago

There are ideas/initiatives for new entities that are meant to provide info on-chain that serves the network in varying capacities. These usually have no hard governing power, although they could, for example the ability to spend a limited amount from the Treasury for their purposes. But that can be handled by mapping their collective origins to some variant of SpendOrigin.

These would generally be instances of the Collective or Ranked Collective pallet to manage membership (apply, nominate, promote, remove), create custom origins, and rank members (e.g. to only give certain tiers voting rights). These all have similar needs that can be abstracted.

Instead of having an Alliance pallet, Ambassador Program pallet, etc., these should just instantiate Ranked Collective for their origin types and ranking, and then use this extension to manage the on-chain info relevant to that collective's audience.

muharem commented 1 year ago

It make sense to me. We just need to understand better the possible use cases to have a good general model for it. We probably can talk about it more offline, and write down the results here.

muharem commented 1 year ago

After looking into the on-chain functionality provided for Alliance (substrate alliance pallet) and Ambassador Program (drafted pallet) I can generalize and describe it in the next way,

  1. stored on-chain members of a collective,
    • an account can request to join (with deposit) or be added by a collective
    • a member can leave a collective
    • a member be removed by a collective and slashed
  2. multiple roles/ranks within a collective
    • a member can be elevated to a higher rank
    • permissions set on a rank level
  3. propose and vote on an operation
    • only members allowed to propose and vote
    • some proposals can be voted only by a higher roles
    • an operation can be executed on behalf of the collective, or members of a particular role
  4. Managed documents by a collective
    • create/add, delete a document of a particular type (events, rule, region list, unscrupulous list of accounts or websites)
    • permissions set on a document type level (signed, signed by member, signed by member of a particular role, voted by all members, etc)

Proposal 1, for first three blocks of functionality (1, 2, 3)

The first three blocks can be implemented by a combination of

Current mismatches:

  1. a member can not request to join, can be only added by some origin (ranked_collective)
  2. no deposit taken for joining to a collective (ranked_collective), hence no slashing

Possible solution for mismatches:

Proposal 2, preimage_registry or cms pallet.

(4) block of the functionality is about managing some information/documents on-chain. It's hard to predict all possible document types, and probably impossible. But one general way to present any data, is a set of bytes. We have preimage pallet to upload any binary data. To categorize (rule, events,..) them and manage we can introduce a new preimage_registry pallet.

API:

Cons:

I believe in most of the cases we can store only the IPFS hash of the document on-chain (IPFS hash uploaded as a preimage). Check the draft implementation for the new pallet in the comment below

Proposal 3, custom pallet per collective

Every collective has a custom pallet implementation, located next to the runtime (not part of substrate), without an aim for generalization.

muharem commented 1 year ago

A concept of a pallet from the "Proposal 2", from the comment above - https://github.com/paritytech/polkadot-sdk/issues/90 The goal is to demonstrate the concept and API.


// pseudo code 

mod runtime {
    enum DocType {
        Personal,
        Events,
        Rule,
    }

    impl pallet::DocConfig<RuntimeOrigin, AccountId> for DocType {
        fn config(&self) -> pallet::DocConfig<RuntimeOrigin, AccountId> {
            match &self {
                DocType::Personal => 
                    // max 10 docs, add: any signed origin, remove: any signed origin && account who added the document
                    pallet::DocConfig::create(10, EnsureSigned, EnsureSigned, pallet::EnsureContributor),
                DocType::Events => 
                    // max 20 docs, add: any member of Alliance, 
                    // remove: AllianceFellows origin - plurality voice given by a referendum.
                    pallet::DocConfig::create(20, EnsureAllianceMember, EnsureAllianceFellows, ()),
                DocType::Rule => 
                    // max 1 docs, add: AllianceFellows origin, remove: AllianceFellows origin
                    pallet::DocConfig::create(1, EnsureAllianceFellows, EnsureAllianceFellows, ()),
            }
        }
    }

    impl pallet::Config for Runtime {
        type MaxDocuments = MaxDocuments,
        type DocType = DocType,
    }
}

mod pallet {
    pub struct DocConfig<OuterOrigin, AccountId> {
        pub max_number: u32,
        pub ensure_add_origin: EnsureOrigin<OuterOrigin>,
        pub ensure_remove_origin: EnsureOrigin<OuterOrigin>,
        // EnsureAccount::ensure_account(origin, doc_contributor), in case a client 
        // wanna let only a document submitter to remove the documents of a particular type.
        pub ensure_remove_account: EnsureAccount<AccountId>,
    }

    pub trait DocConfig<OuterOrigin, AccountId> {
        fn config(&self) -> DocConfig<OuterOrigin, AccountId>;
    }

    pub trait Config<I: 'static = ()>: frame_system::Config {
        type MaxDocuments: Get<u32>;
        type DocType: DocConfig + Clone + Codec + Eq + Debug + TypeInfo + MaxEncodedLen;
    }

    pub type Documents<T: Config<I>, I: 'static = ()> = StorageMap<
        _,
        Blake2_128Concat,
        T::DocType,
        BoundedVec<(PreimageHash, T::AccountId), T::MaxItems>,
        ValueQuery,
    >;

        // will be used to removed expired documents 
    pub type Expire<T: Config<I>, I: 'static = ()> = StorageMap<
        _,
        Blake2_128Concat,
        T::BlockNumber,
        BoundedVec<(PreimageHash, T::DocType>), T::MaxDocuments>,
        ValueQuery,
    >;

    impl<T: Config<I>, I: 'static> Pallet<T, I> {
        pub fn add_doc(origin: OriginFor<T>, doc_type: T::DocType, hash: PreimageHash, maybe_expire: Option<T::BlockNumber>) {
            let config = doc_type::config();
            config.ensure_add_origin::ensure_origin(origin)?;
            ensure!(Self::documents_len(doc_type) > config.max_number);
            ensure!(T::Preimages::len(&hash).is_some(), Error::<T, I>::PreimageNotExist);
            // insert the doc
        }

        pub fn remove_doc(origin: OriginFor<T>, doc_type: T::DocType, hash: PreimageHash) {
            let config = doc_type::config();
            config.ensure_remove_origin::ensure_origin(origin)?;
            let (_, contributor) = Self::document(doc_type, hash).map_err(|_| Error::<T, I>::NotFound)?;
            config.ensure_remove_account::ensure_account(origin, contributor)?;
            // remove the doc
        }

        fn documents_len(doc_type: T::DocType) -> u32 {
            Documents::<T, I>::decode_len(doc_type).unwrap_or(0) as u32
        }

        fn document(doc_type: T::DocType, hash: PreimageHash) -> Result<(PreimageHash, T::AccountId)> {
            // get a doc by type and hash
        }
    }
}
muharem commented 1 year ago

I am moving forward with Proposal 1 and 3. Proposal 2, does not look good enough for now.