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

Optimistic Project Funding #375

Open
lolmcshizz opened this issue Jul 3, 2024 · 24 comments
Open

Optimistic Project Funding #375

lolmcshizz opened this issue Jul 3, 2024 · 24 comments

Comments

@lolmcshizz
Copy link

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:

  • parameterise flexible inflation to drip some % of funds into a “pot” for distribution (achieved through RFC #89)
  • simple pallet that locks DOT & distributes to nominated projects based on locked amount from the pot

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
Copy link
Contributor

bkchr commented Jul 3, 2024

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

@bkchr
Copy link
Contributor

bkchr commented Jul 3, 2024

I assume the "flexible inflation" is probably achieved by this pr: #364?

@kianenigma
Copy link
Contributor

Yes, the part about

parameterise flexible inflation to drip some % of funds into a “pot” for distribution (achieved through polkadot-fellows/RFCs#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
Copy link
Collaborator

Please see a suggestion here: kudos-ink/portal#103 (comment)

@kianenigma
Copy link
Contributor

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
Copy link

marcuspang commented Jul 23, 2024

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

  • MinimumVote: Balance: The minimum amount that can be added to a pot
  • MaxMemoLength: u32: The maximum length of memos attached to pots
  • MaxWhitelistedProjects: u32: The maximum number of projects that can be whitelisted per pot
  • LockPeriod: BlockNumber: The duration for which votes are locked
  • NominationRenewalPeriod: BlockNumber: The period after which nominations must be renewed

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,
}
  • Pots: PotId => PotInfo

  • Votes: (PotId, ProjectId, AccountId) => VoteInfo
    Might not be useful, but if we wish to track the amount of votes per (pot, project, voter), we can do so.

Extrinsics

create_pot

  • Parameters: start_time: BlockNumber, end_time: Option<BlockNumber>, cap: Option<Balance>, time_basis: TimeBasis, owner: AccountId
  • Creates a new funding pot with the specified parameters

whitelist_project

  • Parameters: pot_id: PotId, project_id: ProjectId
  • Adds a project to the whitelist for a specific pot (restricted to pot owner or governance)

remove_whitelisted_project

  • Parameters: pot_id: PotId, project_id: ProjectId
  • Removes a project from the whitelist (restricted to pot owner or governance)

vote_project

  • Parameters: pot_id: PotId, 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

remove_project_vote

  • Parameters: pot_id: PotId, project_id: ProjectId, amount: Balance, is_fund: bool
  • Removes a user's "fund"/"not fund" vote for a project, unlocking the staked amount

distribute_rewards

  • Parameters: pot_id: PotId
  • Triggers the distribution of rewards for a specific pot (can be called by anyone, but respects the time_basis)

set_pot_end_time

  • Parameters: pot_id: PotId, new_end_time: Option<BlockNumber>
  • Updates the end time for a pot (restricted to pot owner or governance)

set_pot_cap

  • Parameters: pot_id: PotId, new_cap: Option<Balance>
  • Updates the cap for a pot (restricted to pot owner or governance)

set_pot_time_basis

  • Parameters: pot_id: PotId, new_time_basis: TimeBasis
  • Updates the time basis for a pot (restricted to pot owner or governance)

set_memo

  • Parameters: pot_id: PotId, project_id: ProjectId, memo: BoundedVec<u8, MaxMemoLength>
  • Updates the memo for a project (restricted to pot owner or governance)

set_owner

  • Parameters: pot_id: PotId, new_owner: Option<AccountId>
  • Updates the owner for a pot (restricted to pot owner or governance)

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.

  • owner can be removed

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
Copy link

ndkazu commented Jul 24, 2024

  • 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:

  • The vote/referendum process is managed outside by another pallet
  • The pallet takes as inputs:
  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.

  • I think only one pot is needed. Projects can be categorized by the amount to be distributed, and in this case (If necessary...but I think this is not an immediate need), we could have spending tracks linked to custom origins to approve the spending.

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
Copy link
Author

  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
Copy link
Author

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
Copy link

marcuspang commented Jul 25, 2024

@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

  • MaxWhitelistedProjects: u32: The maximum number of projects that can be whitelisted per pot
  • VoteLockingPeriod: BlockNumber: The minimum duration for which votes are locked
  • NominationRenewalPeriod: BlockNumber: The period after which nominations must be renewed
  • TimeBasis: TimeBasis: The time basis used to distribute rewards
  • PotAccount: AccountId: The account that holds the funds to be distributed

Types

  • Scheduler: schedule::v3::Named: The scheduler used to schedule the distribution of rewards
  • WhitelistProjectOrigin: Origin: The origin that can whitelist projects
  • ProjectId: AccountId: The identifier of a project

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,
}
  • WhitelistedProjects: BoundedVec<ProjectId, MaxWhitelistedProjects>: The list of whitelisted projects
  • Projects: ProjectId => ProjectInfo: Project information
  • Votes: (AccountId, ProjectId) => VoteInfo: The votes by each account for each whitelisted project
  • LastDistributedBlock: BlockNumber: The last block number at which rewards were distributed

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
Copy link
Contributor

xlc commented Jul 26, 2024

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.

@ndkazu
Copy link

ndkazu commented Jul 26, 2024

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:

  • The full OPF workflow includes inflation information, and the "HOW" for the pot account funding (voting system with convictions maybe, etc...)
  • In this issue/assignment, however, we purely focus on building a pallet that will lock & distribute DOTS from a pot to the accepted/whitelisted projects.

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
Copy link

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
Copy link

ndkazu commented Jul 26, 2024

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
Copy link
Author

@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
Copy link

ndkazu commented Jul 26, 2024

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
Copy link

ndkazu commented Jul 30, 2024

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

  • Events & Tests
  • Benchmarking & weights
  • Proper documentation

@lolmcshizz :

  • Does it match what you are envisioning so far?
  • Should we add custom origins, or keep it for later?

@ndkazu
Copy link

ndkazu commented Jul 31, 2024

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
Copy link
Author

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
Copy link

ndkazu commented Aug 2, 2024

@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
Copy link
Contributor

paritytech/polkadot-sdk#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
Copy link

@ndkazu here is what I imagined the pallet would roughly look like paritytech/polkadot-sdk#5225.

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

@ndkazu
Copy link

ndkazu commented Sep 13, 2024

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

@ndkazu
Copy link

ndkazu commented Sep 18, 2024

@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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants