polkadot-fellows / runtimes

The various runtimes which make up the core subsystems of networks for which the Fellowship is represented.
GNU General Public License v3.0
143 stars 97 forks source link

Optimistic Project Funding #375

Open lolmcshizz opened 5 months ago

lolmcshizz commented 5 months ago

This is a tracking issue for the implementation of Optimistic Project Funding - which was approved by DOT holders in Referendum #712.

Implementation of this feature appears to be achievable in two separate parts:

Relevant links:

Full description of the mechanism that was approved: https://docs.google.com/document/d/1cl6CpWyqX7NCshV0aYT5a8ZTm75PWcLrEBcfk2I1tAA/edit#heading=h.hh40wjcakxp9

Polkadot's economics Forum post: https://forum.polkadot.network/t/polkadots-economics-tools-to-shape-the-forseeable-future/8708?u=lolmcshizz

Project discussion TG: https://t.me/parachainstaking

bkchr commented 5 months ago

@lolmcshizz so what is the idea here? You will search for someone that wants to implement this?

bkchr commented 5 months ago

I assume the "flexible inflation" is probably achieved by this pr: https://github.com/polkadot-fellows/runtimes/pull/364?

kianenigma commented 5 months ago

Yes, the part about

parameterise flexible inflation to drip some % of funds into a “pot” for distribution (achieved through https://github.com/polkadot-fellows/RFCs/pull/89)

Is contained in #364 and RFC89.

The purpose of this issue otherwise should be for someone to be able to understand what needs to be implemented, reach out to @lolmcshizz, and for the fellowship to be aware.

Ideally these wish for change ideas should be not in the fellowship repo, but for now since there is only, we can track it here.

I pointed out in twitter and PBA already that these wish for change impls are great starting point for those that already know FRAME and Polkadot a bit, and are looking for an impactful contribution.

anaelleltd commented 4 months ago

Please see a suggestion here: https://github.com/kudos-ink/portal/issues/103#issuecomment-2220776313

kianenigma commented 4 months ago

I've made a public element room where anyone wishing to have a more sync chat with @lolmcshizz or I to join: https://matrix.to/#/#wish-for-change-impls:parity.io

marcuspang commented 4 months ago

optimistic-funding pallet

Overview

This pallet enables the creation and management of pots that distribute rewards to whitelisted projects based on nominations from token holders.

I have detailed the changes if we are only doing one pot.

Pallet Configuration

Storage Items

Pots: Map<PotId, PotInfo>

struct PotInfo {
    owner: AccountId,
    start_time: BlockNumber,
    end_time: Option<BlockNumber>,
    cap: Option<Balance>, // may not be useful
    time_basis: TimeBasis, // some enum with variants: Daily, Weekly, Monthly, Yearly
    last_distribution_time: Option<BlockNumber>,
    whitelisted_projects: BoundedVec<ProjectId, MaxWhitelistedProjects>,
    total_votes: Balance,
}

struct VoteInfo {
    amount: Balance,
    is_fund bool,
}

Extrinsics

create_pot

whitelist_project

remove_whitelisted_project

vote_project

remove_project_vote

distribute_rewards

set_pot_end_time

set_pot_cap

set_pot_time_basis

set_memo

set_owner

If we are only doing one pot

Pallet Configuration

Add InflationFundingRate: Permill: The rate at which funding is received from inflation

Storage Items

Move PotInfo to parameters, and changes will be made through governance. So, there will be no need for a pots storage item.

Extrinsics

No create_pot or setters for start_time, end_time, cap, time_basis.

Questions

  1. Should we allow creations of multiple pots, each with their own purpose and time basis? Let me know if having multiple pots is out of scope.
  2. Should we allow users to vote multiple times for the same project? If so, how do we resolve "fund" and "not fund" votes?
  3. There should be no way for someone to "not fund" a project to make the balance negative right?
  4. Is there a better way to distribute rewards at a specific timestamp? I'm currently using distribute_rewards extrinsic
ndkazu commented 4 months ago
  • simple pallet that locks DOT & distributes to nominated projects based on a locked amount from the pot

The Pallet is only responsible for locking and distributing Dots, my assumptions are therefore:

  1. Referendum data (convictions, vote results, etc...)
  2. Proportion of DOT inflation

This leads me to think that another pallet will manage parameters such as conviction or NominationRenewalPeriod. We will however need a mock referendum for testing purposes.

Question 3: The Pot should take into account the existential deposit Question 4: I suggest that the project_owner/proposal_creator claim the reward within a given time frame => claim_reward extrinsic.

lolmcshizz commented 4 months ago
  1. I see OPF as one feature that would be distributed from a single pot - I imagine a future where other pots are created for different purposes entirely, but I don't think we need to include that in this implementation.
  2. I would expect votes to be handled in the same way as OpenGov votes are handled - that is, I can re-vote with a different amount but it would override the previous vote. Example, I vote 6x conviction with 1000 DOT, then vote again with 3x 500 DOT - my vote becomes 1500 DOT, the original vote is irrelevant.
  3. Correct - the "not fund" votes can only neutralise any funding that "fund" votes would have allocated to that project, so if a project has 1,000,000 "fund" votes and 1,500,000 "not fund" votes, then they will receive 0 funds and the 1,000,000 worth of "fund" allocation would instead be distributed to the Treasury.
  4. I originally envisaged that rewards would be distributed in the same way as staking rewards are currently distributed, once per epoch - but I am open to other ways. I think doing it by claiming is fine as long as there is some claim_reward_for so that anyone can claim on a project's behalf (for example, if a parachain is one of the projects it wouldn't be great UX for them to have to do a governance referendum just to claim the rewards :)
lolmcshizz commented 4 months ago

Just to clarify @marcuspang - DOT holders can issue both "fund" and "not fund" votes, but they don't have to do both. So for example, I could vote to fund project A with 1,000 DOT, and vote to not fund project B with 1,000 DOT. Or I could just vote to fund project A and not vote to not fund another project - or I could vote to just not fund a specific project, and not offer funding to any project.

marcuspang commented 4 months ago

@lolmcshizz and @ndkazu thank you for the feedback and response.

Here is my revised spec for the pallet, which is much more simplified now. I realised that there is a "scheduler" trait in substrate, which removes the need for manual distribution as discussed above.

Also, I am not sure if there's a better way of keeping track of votes + conviction, but for now I am keeping the values in the pallet as a first draft :)

Pallet Configuration

Constants

Types

Storage

struct VoteInfo {
  amount: Balance,        // Perhaps combine with `conviction` to some auxiliary type like pallet_democracy::Voting
  conviction: Conviction,
  is_fund: bool,          // Whether the vote is "fund" / "not fund"
}

struct ProjectInfo {
  whitelisted_block: BlockNumber,
  total_votes: u32,
}

Extrinsics

Public

  1. vote_project

    • Parameters: project_id: ProjectId, amount: Balance, conviction: Conviction, is_fund: bool
    • Casts a "fund" / "not fund" vote for a project with a specified amount and conviction
    • Subsequent calls to the same project will override the previous vote
    • Updates total vote count of specified project
  2. remove_project_vote

    • Parameters: project_id: ProjectId, amount: Balance
    • Subtracts the amount in a user's vote for a project, unlocking the staked amount
    • Whether the vote was is_fund doesn't affect the subtracted amount
    • Updates total vote count of specified project

WhitelistProjectOrigin

  1. whitelist_project

    • Parameters: project_id: ProjectId
    • Adds a project to the whitelist
  2. remove_whitelisted_project

    • Parameters: project_id: ProjectId
    • Removes a project from the whitelist
xlc commented 4 months ago

I am not sure if conviction makes sense here. I don't follow the whole discussion so can someone explain why it is a useful thing?

The cons I have:

It is always good to start with something simple, with the possibility to extend in the future, than try to get the ultimate version done in V1.

ndkazu commented 4 months ago

From the additional information received here: https://matrix.to/#/#wish-for-change-impls:parity.io, I feel that different things are being mixed up together, so @kianenigma and @lolmcshizz correct me if I am wrong:

For me, this means the following:

  1. The pallet does not need to know about the actual project status, as it will be called by another pallet that will take care of that. Ability to add/remove a project from the list is also out of scope here.
  2. When called, the pallet extrinsic will be provided with a list of accepted/whitelisted project_id/AccountId, and the corresponding DOTs amount: Balance to be locked & distributed.
  3. VoteInfo and the corresponding storage are not necessary.
  4. The struct ProjectInfo could look like:
struct ProjectInfo {
  whitelisted_block: BlockNumber, 
  requested_amount: Balance,
  distributed: bool,
}
  1. The pallet configuration includes Dot's locking period from the moment/block the project_id: ProjectId is received by the pallet.
  2. The distribution time frame is open to discussion, it could be variable depending on the reward amount for exemple (1 time in full, over 3days, or over 1 week...etc).
marcuspang commented 4 months ago

I am not sure if conviction makes sense here. I don't follow the whole discussion so can someone explain why it is a useful thing?

The cons I have:

  • It makes things more complicated.
  • If I read it correctly, higher conviction means more funds to be distributed. This is different to OpenGov referenda, higher conviction wouldn't impact the execution of the proposal. It maybe fine, but I want to make sure the impact is thoroughly considered.

It is always good to start with something simple, with the possibility to extend in the future, than try to get the ultimate version done in V1.

I agree that this is a good first step. Initially, I assumed that the voting of distribution amount would work similar to OpenGov, with conviction / delegation. In which case it would be better to rely on the existing logic in pallet_democracy, but I wasn't sure if this was the best approach.

ndkazu commented 4 months ago

Comment

Inspired by what @marcuspang did, this is another suggestion for the pallet configuration, which reflects my understanding of the task.

Pallet Configuration

Constants

MaxWhitelistedProjects: u32 ⇒ The maximum number of projects that can be whitelisted • LockingPeriod: BlockNumber ⇒ The minimum duration/Buffer time for which funds are locked after project approval/before the reward can be claimed. • TimeBasis: TimeBasis ⇒ The time basis used to distribute rewards • PotAccount: PalletId ⇒ The account that holds the funds to be distributed

Types

ProjectId: AccountId ⇒The identifier of a project

Enums

/// Reward distribution plan (Optional)
pub enum DistributionPlan {
    OneDayUpFront,
    ThreeDays,
    FiveDays,
}

/// Payment status
pub enum PaymentState {
    /// Unclaimed
    Unclaimed
        /// Claimed & Pending.
    Pending,
    /// Claimed & Paid.
    Completed,
    /// Claimed but Failed.
    Failed,
}

Storages & Structs

/// Information relative to a given payment/distribution
pub struct PaymentInfo{
    /// The asset amount of the spend.
    pub amount: Balance,
    /// The beneficiary of the spend.
    pub ProjectId: ProjectId,
    /// The block number from which the spend can be claimed
    pub valid_from: BlockNumber,
    /// The status of the payout.
    pub status: PaymentState,
        /// Payment Plan (Optional)
    pub plan: SpendingPlan,
    /// Amount already paid
    pub paid: Balance>,
}

struct ProjectInfo {
  whitelisted_block: BlockNumber, 
  requested_amount: Balance,
  distributed: bool,
}

WhitelistedProjects: BoundedVec<ProjectId, MaxWhitelistedProjects>⇒ The list of whitelisted projects Projects: ProjectId ⇒ ProjectInfo: Project information LastDistributedBlock: BlockNumber ⇒ The last block number at which rewards were distributed

Extrinsics

Public

claim_reward_for: Claim the reward for a specific project. Accessible by any signed_origin

lolmcshizz commented 4 months ago

@ndkazu - yes to be clear, the "funding" part is outside of the scope - we only need (as described in the channel by @kianenigma) a key-less account that the pallet controls.

The pallet does not need to know about the actual project status, as it will be called by another pallet that will take care of that. Ability to add/remove a project from the list is also out of scope here.

We will need some storage containing all whitelisted projects, which can be added/ removed using some track on OpenGov - I don't know where this "list" needs to be but it needs to be somewhere.

The struct ProjectInfo could look like: struct ProjectInfo { whitelisted_block: BlockNumber, requested_amount: Balance, distributed: bool, }

Can you clarify what is meant by requested_amount here? Projects will not be requesting any funding specifically - the amount is determined by the portion of votes that a project receives out of the total number of votes (minus any NAY votes).

The distribution time frame is open to discussion, it could be variable depending on the reward amount for exemple (1 time in full, over 3days, or over 1 week...etc).

Is this adding more complexity? I am fine with just daily distribution the same we have in staking right now.

I am not sure if conviction makes sense here. I don't follow the whole discussion so can someone explain why it is a useful thing?

The cons I have:

It makes things more complicated. If I read it correctly, higher conviction means more funds to be distributed. This is different to OpenGov referenda, higher conviction wouldn't impact the execution of the proposal. It maybe fine, but I want to make sure the impact is thoroughly considered. It is always good to start with something simple, with the possibility to extend in the future, than try to get the ultimate version done in V1.

@xlc the conviction is important here in the same way as in OpenGov - higher conviction means a longer lock so your votes carry more weight. Yes projects would receive more funding if their votes are of higher conviction than those of other projects - but imo this is a positive feature.

ndkazu commented 4 months ago

We will need some storage containing all whitelisted projects, which can be added/ removed using some track on OpenGov - I don't know where this "list" needs to be but it needs to be somewhere.

I don't disagree, this storage was even defined as follows by @marcuspang, and I kept it:

WhitelistedProjects: BoundedVec<ProjectId, MaxWhitelistedProjects>⇒ The list of whitelisted projects

My point here is that it should be populated by an external source/pallet, but we will probably need a function to populate it for the pallet testing.

Can you clarify what is meant by requested_amount here? Projects will not be requesting any funding specifically - the amount is determined by the portion of votes that a project receives out of the total number of votes (minus any NAY votes).

@lolmcshizz , you are correct, it should be amount instead of requested_amount

The distribution time frame is open to discussion, it could be variable depending on the reward amount for exemple (1 time in full, over 3days, or over 1 week...etc).

Is this adding more complexity? I am fine with just daily distribution the same we have in staking right now.

I think you are also right here: let's keep it simple.

ndkazu commented 4 months ago

Ok, I prepared the main structure of the pallet with a few modifications compared to the plan described above. The next steps are:

@lolmcshizz :

ndkazu commented 4 months ago

Hello, I would like to know your opinion on the following: Although I think the OPF voting system could just be added in the existing pallet, having the rewards distribution process in a different pallet makes more sense in my opinion.

Motivation

  1. Future-proof solution, considering the multiple ways rewards could be/are being distributed
  2. Claimable vs Automated distribution is not necessarily a clear-cut decision
  3. I'm trying to make a case for the work I did (let's be honest!!)

Please let me know what you think.

lolmcshizz commented 4 months ago

As this is more of a technical implementation question than impacting how it works for users, I would defer to @kianenigma here for guidance

ndkazu commented 4 months ago

@lolmcshizz , what do you think about the following timeline for opf_voting:


|--------------------Nomination_Period_0--------------------->|--------------------Nomination_Period_1--------------------->|

|--------Voting_Period-------->|--------Voting_locked-------->|--------Voting_Period-------->|--------Voting_locked-------->|

|----------------------------->|-----Rewards_Distribution---->|------------------------------->|-----Rewards_Distribution--->|
kianenigma commented 4 months ago

https://github.com/paritytech/polkadot-sdk/pull/5162

I am glad to see this, but let's note that this pallet can also live elsewhere, perhaps here or in anyone's repo, given that we have proper releases now :)

Future-proof solution, considering the multiple ways rewards could be/are being distributed

I don't have strong opinions about whether reward should be in a separate pallet or not, but I would probably reside to one of possible.

Claimable vs Automated distribution is not necessarily a clear-cut decision

Automated solutions are generally more hairy. I suggest doing a claim-able only impl for now, and later on you can always use #[pallet::task] or on_idle to soft-automate it :)

If more specific tech questions, let's continue in your draft PR as it is easier to review the whole code.

marcuspang commented 4 months ago

@ndkazu here is what I imagined the pallet would roughly look like https://github.com/paritytech/polkadot-sdk/pull/5225.

After your recent changes, I think the main difference now is storage and how claims are being kept track of

ndkazu commented 2 months ago

Hello, I recently noticed & reverted unwanted changes, probably added by the command cargo fmt at some point. Review request is already out.

ndkazu commented 2 months ago

@kianenigma, for now, I am using a constant defined in the runtime for the total reward to be distributed from the pot to the selected projects during each round, but I am guessing there will be (or already is...) a pallet/function to get the portion of inflation to be distributed. Is this correct?

kianenigma commented 5 days ago

Re-posting something I have discussed with @lolmcshizz: I genuinely think that merging and getting this to production in the midst of moving everything outside of the relay chain to AH will be very, very hard.

A more forward-looking suggestion: Implement this as a solidity contract. Once Solidity contracts are live (~mid-2025), all that the fellowship would then have to do is to forward a small part of the inflation to this contract's account.