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

feat: psp22 example DAO contract #407

Open
wants to merge 51 commits into
base: main
Choose a base branch
from
Open

Conversation

ndkazu
Copy link

@ndkazu ndkazu commented Dec 13, 2024

Description

Issue #337
This contract implements a Decentralized Autonomous Organization using Psp22.
The key functionalities include:

  • Membership Management: It maintains a registry of DAO members.
  • Proposal Lifecycle: The contract manages the creation, voting, and execution of proposals. Each proposal includes details like description, voting period, vote tallies, execution status, beneficiary, and amount to be awarded.
  • Voting Mechanism: It implements a voting system where members can cast votes with their balance on proposals. The contract tracks voting periods and maintains vote counts for each proposal.
  • Token Integration: The DAO is associated with a specific Psp22 token_id.
  • Governance Parameters: governance parameters such as voting periods are customizable.
  • Vote Tracking: The contract keeps track of when members last voted.
  • Proposal Execution: Once a proposal's voting period ends and it passes, the contract handles the execution of the proposal: Transfer funds to the chosen beneficiary.]

ToDo

  • Tests
  • Create a new Dao works
  • A new member joins the Dao works
  • Member creates a proposal works
  • Members vote system works
  • Proposal enactment works
  • Voting twice for the same proposal fails
  • Vote fails if not a member
  • Vote fails if voting period ended
  • Proposal enactment fails if proposal was rejected
  • Same proposal consecutive claim fails
  • Others
  • Events
  • in-code documentation

Remark

Considering the fact that another issue is asking for psp22 examples using cross contract call, I decided to use api::... instead of the macro contract_ref!.

@ndkazu ndkazu marked this pull request as draft December 13, 2024 11:19
@ndkazu ndkazu changed the title Example using psp22 contract - Dao Contract using Psp22 feat: Example using psp22 contract - Dao Contract using Psp22 Dec 13, 2024
@ndkazu ndkazu marked this pull request as ready for review December 15, 2024 01:22
@ndkazu
Copy link
Author

ndkazu commented Dec 15, 2024

@chungquantin , I wrote a first test, but it's failing with an error that I don't fully understand: Encountered unexpected missing chain extension method: UnregisteredChainExtension. Can you help please?

@chungquantin
Copy link
Collaborator

@chungquantin , I wrote a first test, but it's failing with an error that I don't fully understand: Encountered unexpected missing chain extension method: UnregisteredChainExtension. Can you help please?

Are you using pop-drink to write the test?

@ndkazu
Copy link
Author

ndkazu commented Dec 15, 2024

@chungquantin , I wrote a first test, but it's failing with an error that I don't fully understand: Encountered unexpected missing chain extension method: UnregisteredChainExtension. Can you help please?

Are you using pop-drink to write the test?

No, should I? I will look into it.

@ndkazu
Copy link
Author

ndkazu commented Dec 15, 2024

@chungquantin , I wrote a first test, but it's failing with an error that I don't fully understand: Encountered unexpected missing chain extension method: UnregisteredChainExtension. Can you help please?

Are you using pop-drink to write the test?

No, should I? I will look into it.

I am now using pop-drink, I am also using the fungibles example tests to navigate through this. However, at the following line:
#[drink::contract_bundle_provider]
I keep getting the following error:
message: Error building contract: lib section not found
I will need some help to move forward.

@Daanvdplas
Copy link
Collaborator

Encountered unexpected missing chain extension method: UnregisteredChainExtension

This is because when a contract uses the pop-api, it interacts with Pop Network, in this case the fungibles api; functionality provided by the chain to create, manage and interact with fungible tokens.

As a result, the tests need something like a chain running. This is where pop-drink comes in, it provides a pop runtime for your contract to interact with, and thus be able to write and run tests for your contract.

@Daanvdplas
Copy link
Collaborator

Daanvdplas commented Dec 15, 2024

message: Error building contract: lib section not found
I will need some help to move forward.

Add the following two lines in your Cargo.toml (before features section):

[lib]
path = "src/lib.rs"

As well as the following line at the top of your contract:

#![cfg_attr(not(feature = "std"), no_std, no_main)]

To generate a new contract with all the requirements you can use the pop cli: pop new contract :)

In your Cargo.toml file you also need to add an author.

Copy link
Collaborator

@Daanvdplas Daanvdplas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The contract looks great! I left some fixes to make your contract compile and left comment regarding the use of Strings in contracts. Don't forget to use cargo test --release to run the pop drink tests.

In terms of the dao logic, it would be great if you could document how the should dao operates. This would make it easier in the future to review the contract.

And super awesome that you are developing a contract using the api. Please note down all the things that are difficult in your journey so that we can improve them!! In general for ink! but also using the api, the docs, pop drink, everything :):):)

pop-api/examples/dao/src/lib.rs Outdated Show resolved Hide resolved
pop-api/examples/dao/src/lib.rs Outdated Show resolved Hide resolved
pop-api/examples/dao/src/lib.rs Outdated Show resolved Hide resolved
@chungquantin chungquantin changed the title feat: Example using psp22 contract - Dao Contract using Psp22 feat: psp22 example DAO contract Dec 17, 2024
@ndkazu
Copy link
Author

ndkazu commented Dec 17, 2024

I'm struggling with pop-drink:
When testing the function join, I prepared a call in a function, used it in the join_dao_works test, and it worked perfectly. I tried to do the EXACT same thing for the member_create_proposal_works test, but then I get the error:

thread 'tests::member_create_proposal_works' panicked at /home/kazu/.cargo/git/checkouts/pop-drink-006524d3c8dd1ad9/cfbc763/crates/pop-drink/src/lib.rs:192:8:
Expected call to revert or return a value

Removing the comment at assert_ok!(...) will show the error.
I tried so many things at this point, I am definitely missing something (obvious?) but I don't know what.
Need help....

@chungquantin
Copy link
Collaborator

@ndkazu pop-drink tests must be run with --release flag

cargo test --release

@ndkazu
Copy link
Author

ndkazu commented Dec 26, 2024

@ndkazu pop-drink tests must be run with --release flag

cargo test --release

Thanks! forgot about that one.

@ndkazu
Copy link
Author

ndkazu commented Dec 27, 2024

@chungquantin & @Daanvdplas, review requested: struggling with the use of a RuntimeCall for proposal enactment.

@ndkazu
Copy link
Author

ndkazu commented Dec 29, 2024

@chungquantin & @Daanvdplas, review requested: struggling with the use of a RuntimeCall for proposal enactment.

RuntimeCall version now working => Good step for a code review.

Copy link
Collaborator

@chungquantin chungquantin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So far so good! Haven't reviewed the test yet.

pop-api/examples/dao/src/lib.rs Outdated Show resolved Hide resolved
pop-api/examples/dao/src/lib.rs Outdated Show resolved Hide resolved
pop-api/examples/dao/src/lib.rs Outdated Show resolved Hide resolved
pop-api/examples/dao/src/lib.rs Outdated Show resolved Hide resolved
pop-api/examples/dao/src/lib.rs Outdated Show resolved Hide resolved
pop-api/examples/dao/src/lib.rs Outdated Show resolved Hide resolved
pop-api/examples/dao/src/lib.rs Outdated Show resolved Hide resolved
pop-api/examples/dao/src/lib.rs Outdated Show resolved Hide resolved
pop-api/examples/dao/src/lib.rs Outdated Show resolved Hide resolved

// RuntimeCall transfer, you must comment api::transfer_from() below
let _ = self.env()
.call_runtime(&RuntimeCall::Fungibles(FungiblesCall::TransferFrom {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To extend the functionality of the runtime call and not limit it only to TransferFrom, you can allow the creator of the proposal to provide parameters for the runtime call in bytes.

Copy link
Author

@ndkazu ndkazu Dec 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chungquantin , could you point me to an example for this please.
I was thinking about adding a field call: Option<RuntimeCall>, to the struct Proposal (possibly call: Option<Box<RuntimeCall>> for variable lifetime considerations), and then we would have:

#[ink(message)]
		pub fn create_proposal(
			&mut self,
			beneficiary: AccountId,
			amount: Balance,
			mut description: Vec<u8>,
			call: RuntimeCall,
		) -> Result<(), Error> {
                        ...
                        let wrapped_call = Some(call);
			self.proposal_count = self.proposal_count.saturating_add(1);
			let mut proposal =
				Proposal { proposal_id: self.proposal_count, call: wrapped_call,  ..Default::default() };
                        ...
}

and then, in execute_proposal, we would just do:

                let call = proposal.call.clone().expect("There should be a call");
		let _ = self.env().call_runtime(&call).map_err(EnvError::from);

The problem is then how do I test it, the Type RuntimeCall is not easily convertible to String, but I saw a crate that converts enums to strings. let me know.

@ndkazu ndkazu requested a review from chungquantin December 31, 2024 13:57
@ndkazu
Copy link
Author

ndkazu commented Jan 6, 2025

Hello @chungquantin & @Daanvdplas , I am still trying to solve this and and need some feedback please: the RuntimeCall enum conversion to bytes requires the trait Serialize to be implemented. the problem is that this trait is not implemented for ink_primitive::AccountId, which itself is used in the call I am trying to test...
I am just trying to do an enum-->bytes-->string conversion to be able to run the test.

Copy link
Collaborator

@Daanvdplas Daanvdplas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What an amazing work! Bravo for writing the first contract using the pop api and write tests using pop drink, you are the first one!!

Before doing a proper review on the contract there needs to be agreed on what the example contract should be. For an example contract it is important to define a clear use case. Right now there are few options we can finalise towards:

  1. A dao where people can vote on proposals where the native token is transferred to a given account (makes me think of the tips pallet from the sdk)
  2. A dao where people can vote on proposals where any kind of RuntimeCall is called
  3. ...

Option 1 could for example turn into one of the following examples:

  • Community Fund Management: Manage a treasury where members can propose and vote on funding community projects.
  • Grant Distribution: Allocate funds to developers or projects based on proposals and voting.

Option 2 gets a lot more complex to find an easy to explain use case. But perhaps OpenGov in a contract. This however will require more work and might be too complex for an example.

It doesn't have to be complicated or perfect at all, but people should understand what the contract example is about by looking at the title or the introduction, for them to get excited to learn about this specific example :)

I will be following this issue closer so that we can agree on the use case for the example. Then perhaps it requires some changes, but then I am ready to give it a review!!

Super excited to see this example in the repo!

@Daanvdplas
Copy link
Collaborator

Daanvdplas commented Jan 8, 2025

One additional point is that I don't understand the incentive for joining the dao. If I buy myself in and all the dao's tokens are spent to e.g. projects, what do I win from this? It could be a charity funding dao perhaps, this would have to be included in the use case however.

I don't want to overcomplicate the example at all, but the use case should make sense and be complete otherwise I think it would demotivate people to look into the example.

More then happy to help here in every way!!

@ndkazu
Copy link
Author

ndkazu commented Jan 8, 2025

What an amazing work! Bravo for writing the first contract using the pop api and write tests using pop drink, you are the first one!!

Before doing a proper review on the contract there needs to be agreed on what the example contract should be. For an example contract it is important to define a clear use case. Right now there are few options we can finalise towards:

  1. A dao where people can vote on proposals where the native token is transferred to a given account (makes me think of the tips pallet from the sdk)
  2. A dao where people can vote on proposals where any kind of RuntimeCall is called
  3. ...

Option 1 could for example turn into one of the following examples:

  • Community Fund Management: Manage a treasury where members can propose and vote on funding community projects.
  • Grant Distribution: Allocate funds to developers or projects based on proposals and voting.

Option 2 gets a lot more complex to find an easy to explain use case. But perhaps OpenGov in a contract. This however will require more work and might be too complex for an example.

It doesn't have to be complicated or perfect at all, but people should understand what the contract example is about by looking at the title or the introduction, for them to get excited to learn about this specific example :)

I will be following this issue closer so that we can agree on the use case for the example. Then perhaps it requires some changes, but then I am ready to give it a review!!

Super excited to see this example in the repo!

Thank you for the feedback, @Daanvdplas!! I agree that as an example, the contract should be relatively simple, using too many advanced features in one contract could be daunting for people "Just diving in...".
In the previous commit I'm actually going in the option_1 direction. The latest commit is the one going in the option_2 direction ⇒ maybe it should be 2 different PRs?
As for the incentive to join the PR, I feel like it is a bit like what is being done here with the Optimistic Project Funding Pallet in substrate (still in work...): Members of the community vote for the funding of projects they want to support. The funds themselves come from a percentage of the inflation. So basically this contract (Option_1) is a simplified/rough version of the OPF_pallet (A Staking contract would be used to provide an incentive...). But yeah finding a clearer incentive concept would be nice. I will think about it a bit more.

@Daanvdplas
Copy link
Collaborator

How do you become a member of the community that holds the funds?

@ndkazu
Copy link
Author

ndkazu commented Jan 9, 2025

How do you become a member of the community that holds the funds?

@Daanvdplas in OPF you only have to be a DOT holder: you lock some funds as voting power during the voting period.
I also wanted to add that in this kind of Dao (Option_1), the incentive is mainly provided by the project requesting funding and not by the Dao itself: What kind of services/Token use-case/benefits, will they provide to the Dao_Token holders and the Eco-system?
I just finished implementing the customized runtime calls, which corresponds to option_2, but I agree with what you said on Telegram: for an example, option_1 is better: simpler, easier to explain and modify...

@ndkazu ndkazu requested a review from Daanvdplas January 9, 2025 03:00
@Daanvdplas
Copy link
Collaborator

I think we should go with option 1. Perhaps we can find a popular dao contract example that functions the same / similar and see how they tell the story of the use case and tell it in a way that it doesn't matter that it is perfectly thought out, as that is the feeling I have now (regarding the incentive to join this dao). Another solution is to add ideas for how to solve this and provide it in the readme!

Let me know if you agree with taking option 1 then I can do a review.

@ndkazu
Copy link
Author

ndkazu commented Jan 9, 2025

I think we should go with option 1. Perhaps we can find a popular dao contract example that functions the same / similar and see how they tell the story of the use case and tell it in a way that it doesn't matter that it is perfectly thought out, as that is the feeling I have now (regarding the incentive to join this dao). Another solution is to add ideas for how to solve this and provide it in the readme!

Let me know if you agree with taking option 1 then I can do a review.

@Daanvdplas I agree, and opened another PR for option_1 as the latest commit here is oriented toward option_2: we can keep it for later.

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

Successfully merging this pull request may close these issues.

3 participants