-
Notifications
You must be signed in to change notification settings - Fork 10
feat: psp22 example DAO contract #407
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
Closed
Closed
Changes from 7 commits
Commits
Show all changes
54 commits
Select commit
Hold shift + click to select a range
d8f3c22
Dao contract
ndkazu 5cbb44a
Added some events
ndkazu bb060f1
removed absent module
ndkazu 30637ae
Added some in-code documentation
ndkazu e6c9edd
Corrected index error
ndkazu 058e53b
Problem with first test
ndkazu ff2b300
Making sense of pop-drink for testing
ndkazu 2505f89
Added lib.rs to Cargo, but still...
ndkazu 6f653a9
Added the correct deploy() function in tests
ndkazu aacbf87
Put a limitation of description string length
ndkazu f746400
chore: add missing authors
chungquantin 285c178
new member test added
ndkazu 34002d0
create_proposal test
ndkazu d17096c
Calls prepared for testing
ndkazu 3f2c114
ReadMe
ndkazu 22d46e3
ReadME
ndkazu b02f232
create_proposal_test
ndkazu c08b335
Another test
ndkazu 7a1563b
Enactment test
ndkazu 9eca1b2
one more test
ndkazu b0d6194
Reverted some changes
ndkazu f8910ce
Reverted some changes
ndkazu 94d884e
Documented the failing test: proposal_enactment_works
ndkazu 00d76c0
Another test...
ndkazu 8de7a7d
tests
ndkazu 5e91679
refactored the code & added another test
ndkazu 77328df
Treasury balance check & another test
ndkazu a8206d4
Another test
ndkazu 56e622d
cargo fmt
ndkazu 4ad34f4
Final test
ndkazu 0cd9573
Applied some fixes related to the code review
ndkazu be3759f
Added ProposalStatus enum
ndkazu a34f0d1
Added descriptions for errors
ndkazu 5e8e8e4
Review correction
ndkazu ede1283
cargo clippy
ndkazu 425d757
Merge branch 'r0gue-io:main' into psp22Example
ndkazu 0e2db17
update ink version
ndkazu 21b13c5
cargo clippy --fix
ndkazu 34ba35c
Some clean up
ndkazu bd11e86
All tests pass
ndkazu 905705c
cargo fmt
ndkazu 0841c32
Refactored code, implemented Default trait for Proposal
ndkazu 562b17f
cargo fmt
ndkazu eaae705
Preparations for use of RuntimeCall
ndkazu adb72f9
Use transfer_from instead of transfer for runtime_call
ndkazu 290d0ec
Corrected test mistake, using transfer_from instead of transfer
ndkazu bad1126
RuntimeCall working
ndkazu 32ecca5
Corrections
ndkazu c357a07
Code re-factoring
ndkazu c0f9f9d
RuntimeCall conversion problem
ndkazu 2526c04
customised RuntimeCalls works
ndkazu 9e7313a
Merge branch 'main' into psp22Example
ndkazu 026b909
Applied the corrections
ndkazu 739a235
cargo.toml update
ndkazu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| [package] | ||
| name = "dao" | ||
| version = "0.1.0" | ||
| edition = "2021" | ||
|
|
||
| [dependencies] | ||
| ink = { version = "=5.0.0", default-features = false, features = ["ink-debug"] } | ||
| pop-api = { path = "../../../pop-api", default-features = false, features = [ | ||
| "fungibles", | ||
| ] } | ||
| scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } | ||
| scale-info = { version = "2.3", default-features = false, features = ["derive"], optional = true } | ||
|
|
||
| [dev-dependencies] | ||
| drink = { package = "pop-drink", git = "https://github.com/r0gue-io/pop-drink" } | ||
| env_logger = { version = "0.11.3" } | ||
| serde_json = "1.0.114" | ||
|
|
||
| # TODO: due to compilation issues caused by `sp-runtime`, `frame-support-procedural` and `staging-xcm` this dependency | ||
| # (with specific version) has to be added. Will be tackled by #348, please ignore for now. | ||
| frame-support-procedural = { version = "=30.0.1", default-features = false } | ||
| sp-runtime = { version = "=38.0.0", default-features = false } | ||
| staging-xcm = { version = "=14.1.0", default-features = false } | ||
|
|
||
| [features] | ||
| default = ["std"] | ||
| e2e-tests = [] | ||
| ink-as-dependency = [] | ||
| std = [ | ||
| "ink/std", | ||
| "pop-api/std", | ||
| "scale-info/std", | ||
| "scale/std", | ||
| ] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,303 @@ | ||
| use ink::{ | ||
| prelude::{string::String, vec::Vec}, | ||
| storage::Mapping, | ||
| }; | ||
| use pop_api::{ | ||
| primitives::TokenId, | ||
| v0::fungibles::{ | ||
| self as api, | ||
| events::{Approval, Created, Transfer}, | ||
| Psp22Error, | ||
| }, | ||
| }; | ||
|
|
||
| #[cfg(test)] | ||
| mod tests; | ||
|
|
||
| #[ink::contract] | ||
| mod dao { | ||
| use super::*; | ||
|
|
||
| /// Structure of the proposal used by the Dao governance sysytem | ||
| #[derive(scale::Decode, scale::Encode, Debug)] | ||
| #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] | ||
|
ndkazu marked this conversation as resolved.
Outdated
|
||
| pub struct Proposal { | ||
| // Description of the proposal | ||
| description: String, | ||
|
|
||
| // Beginnning of the voting period for this proposal | ||
| vote_start: BlockNumber, | ||
|
|
||
| // End of the voting period for this proposal | ||
| vote_end: BlockNumber, | ||
|
|
||
| // Balance representing the total votes for this proposal | ||
| yes_votes: Balance, | ||
|
|
||
| // Balance representing the total votes against this proposal | ||
| no_votes: Balance, | ||
|
|
||
| // Flag that indicates if the proposal was executed | ||
| executed: bool, | ||
|
|
||
| // AccountId of the recipient of the proposal | ||
|
ndkazu marked this conversation as resolved.
Outdated
|
||
| beneficiary: AccountId, | ||
|
|
||
| // Amount of tokens to be awarded to the beneficiary | ||
| amount: Balance, | ||
|
|
||
| // Identifier of the proposal | ||
| proposal_id: u32, | ||
| } | ||
|
|
||
| /// Representation of a member in the voting system | ||
| #[derive(scale::Decode, scale::Encode)] | ||
| #[cfg_attr(feature = "std", derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout))] | ||
|
ndkazu marked this conversation as resolved.
Outdated
|
||
| pub struct Member { | ||
| // Stores the member's voting influence by using his balance | ||
| voting_power: Balance, | ||
|
|
||
| // Keeps track of the last vote casted by the member | ||
| last_vote: BlockNumber, | ||
| } | ||
|
|
||
| /// Structure of a DAO (Decentralized Autonomous Organization) | ||
| /// that uses Psp22 to manage the Dao treasury and funds projects | ||
| /// selected by the members through governance | ||
| #[ink(storage)] | ||
| pub struct Dao { | ||
| // Funding proposals | ||
| proposals: Vec<Proposal>, | ||
|
|
||
| // Mapping of AccountId to Member structs, representing DAO membership. | ||
| members: Mapping<AccountId, Member>, | ||
|
|
||
| // Mapping tracking the last time each account voted. | ||
| last_votes: Mapping<AccountId, Timestamp>, | ||
|
|
||
| // Duration of the voting period | ||
| voting_period: BlockNumber, | ||
|
|
||
| // Identifier of the Psp22 token associated with this DAO | ||
| token_id: TokenId, | ||
| } | ||
|
|
||
| impl Dao { | ||
| /// Instantiate a new Dao contract and create the associated token | ||
|
ndkazu marked this conversation as resolved.
|
||
| /// | ||
| /// # Parameters: | ||
| /// - `token_id` - The identifier of the token to be created | ||
| /// - `voting_period` - Amount of blocks during which members can cast their votes | ||
| /// - `min_balance` - The minimum balance required for accounts holding this token. | ||
| // The `min_balance` ensures accounts hold a minimum amount of tokens, preventing tiny, | ||
| // inactive balances from bloating the blockchain state and slowing down the network. | ||
| #[ink(constructor, payable)] | ||
| pub fn new( | ||
| token_id: TokenId, | ||
| voting_period: BlockNumber, | ||
| min_balance: Balance, | ||
| ) -> Result<Self, Psp22Error> { | ||
| let instance = Self { | ||
| proposals: Vec::new(), | ||
| members: Mapping::default(), | ||
| last_votes: Mapping::default(), | ||
| voting_period, | ||
| token_id: token_id, | ||
| }; | ||
| let contract_id = instance.env().account_id(); | ||
| api::create(token_id, contract_id, min_balance).map_err(Psp22Error::from)?; | ||
| instance.env().emit_event(Created { | ||
| id: token_id, | ||
| creator: contract_id, | ||
| admin: contract_id, | ||
| }); | ||
|
|
||
| Ok(instance) | ||
| } | ||
|
|
||
| /// Allows members to create new spending proposals | ||
| /// | ||
| /// # Parameters | ||
| /// - `beneficiary` - The account that will receive the payment | ||
| /// if the proposal is accepted. | ||
| /// - `amount` - Amount requested for this proposal | ||
| /// - `description` - Description of the proposal | ||
| #[ink(message)] | ||
| pub fn create_proposal( | ||
| &mut self, | ||
| beneficiary: AccountId, | ||
| amount: Balance, | ||
| description: String, | ||
|
ndkazu marked this conversation as resolved.
Outdated
|
||
| ) -> Result<(), Error> { | ||
| let _caller = self.env().caller(); | ||
| let current_block = self.env().block_number(); | ||
| let proposal_id: u32 = self.proposals.len().try_into().unwrap_or(0u32); | ||
| let vote_end = current_block.checked_add(self.voting_period).ok_or(Error::ArithmeticOverflow)?; | ||
| let proposal = Proposal { | ||
|
ndkazu marked this conversation as resolved.
Outdated
|
||
| description, | ||
| vote_start: current_block, | ||
| vote_end, | ||
| yes_votes: 0, | ||
| no_votes: 0, | ||
| executed: false, | ||
| beneficiary, | ||
| amount, | ||
| proposal_id, | ||
| }; | ||
|
|
||
| self.proposals.push(proposal); | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Allows Dao's members to vote for a proposal | ||
| /// | ||
| /// # Parameters | ||
| /// - `proposal_id` - Identifier of the proposal | ||
| /// - `approve` - Indicates whether the vote is in favor (true) or against (false) the | ||
| /// proposal. | ||
| #[ink(message)] | ||
| pub fn vote(&mut self, proposal_id: u32, approve: bool) -> Result<(), Error> { | ||
| let caller = self.env().caller(); | ||
| let current_block = self.env().block_number(); | ||
|
|
||
| let proposal = | ||
| self.proposals.get_mut(proposal_id as usize).ok_or(Error::ProposalNotFound)?; | ||
|
|
||
| if current_block < proposal.vote_start || current_block > proposal.vote_end { | ||
| return Err(Error::VotingPeriodEnded); | ||
| } | ||
|
|
||
| let member = self.members.get(caller).ok_or(Error::NotAMember)?; | ||
|
|
||
| if member.last_vote >= proposal.vote_start { | ||
| return Err(Error::AlreadyVoted); | ||
| } | ||
|
|
||
| if approve { | ||
| proposal.yes_votes.checked_add(member.voting_power).ok_or(Error::ArithmeticOverflow)?; | ||
| } else { | ||
| proposal.no_votes.checked_add(member.voting_power).ok_or(Error::ArithmeticOverflow)?; | ||
| } | ||
|
|
||
| self.members.insert( | ||
| caller, | ||
| &Member { voting_power: member.voting_power, last_vote: current_block }, | ||
| ); | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| /// Enact a proposal approved by the Dao members | ||
| /// | ||
| /// # Parameters | ||
| /// - `proposal_id` - Identifier of the proposal | ||
| #[ink(message)] | ||
| pub fn execute_proposal(&mut self, proposal_id: u32) -> Result<(), Error> { | ||
| let vote_end = self | ||
| .proposals | ||
| .get(proposal_id as usize) | ||
| .ok_or(Error::ProposalNotFound)? | ||
| .vote_end; | ||
|
|
||
| // Check the voting period | ||
| if self.env().block_number() <= vote_end { | ||
|
ndkazu marked this conversation as resolved.
Outdated
|
||
| return Err(Error::VotingPeriodNotEnded); | ||
| } | ||
|
|
||
| // If we've passed the checks, now we can mutably borrow the proposal | ||
| let proposal_id_usize = proposal_id as usize; | ||
| let proposal = self.proposals.get(proposal_id_usize).ok_or(Error::ProposalNotFound)?; | ||
|
|
||
| if proposal.executed { | ||
| return Err(Error::ProposalAlreadyExecuted); | ||
| } | ||
|
|
||
| if proposal.yes_votes > proposal.no_votes { | ||
| let contract = self.env().account_id(); | ||
| // ToDo: Check that there is enough funds in the treasury | ||
| // Execute the proposal | ||
| api::transfer(self.token_id, proposal.beneficiary, proposal.amount) | ||
| .map_err(Psp22Error::from)?; | ||
| self.env().emit_event(Transfer { | ||
| from: Some(contract), | ||
| to: Some(proposal.beneficiary), | ||
| value: proposal.amount, | ||
| }); | ||
| self.env().emit_event(Approval { | ||
| owner: contract, | ||
| spender: contract, | ||
| value: proposal.amount, | ||
| }); | ||
|
|
||
| if let Some(proposal) = self.proposals.get_mut(proposal_id_usize) { | ||
| proposal.executed = true; | ||
| } | ||
| Ok(()) | ||
| } else { | ||
| Err(Error::ProposalRejected) | ||
| } | ||
| } | ||
|
|
||
| /// Allows a user to become a member of the Dao | ||
| /// by transferring some tokens to the DAO's treasury. | ||
| /// The amount of tokens transferred will be stored as the | ||
| /// voting power of this member. | ||
| /// | ||
| /// # Parameters | ||
| /// - `amount` - Balance transferred to the Dao and representing | ||
| /// the voting power of the member. | ||
|
|
||
|
ndkazu marked this conversation as resolved.
Outdated
|
||
| #[ink(message)] | ||
| pub fn join(&mut self, amount: Balance) -> Result<(), Error> { | ||
|
ndkazu marked this conversation as resolved.
|
||
| let caller = self.env().caller(); | ||
| let contract = self.env().account_id(); | ||
| api::transfer_from(self.token_id, caller.clone(), contract.clone(), amount) | ||
| .map_err(Psp22Error::from)?; | ||
| self.env().emit_event(Transfer { | ||
| from: Some(caller), | ||
| to: Some(contract), | ||
| value: amount, | ||
| }); | ||
|
|
||
| let member = | ||
| self.members.get(caller).unwrap_or(Member { voting_power: 0, last_vote: 0 }); | ||
|
|
||
| let voting_power = member.voting_power.checked_add(amount).ok_or(Error::ArithmeticOverflow)?; | ||
| self.members.insert( | ||
| caller, | ||
| &Member { voting_power, last_vote: member.last_vote }, | ||
| ); | ||
|
|
||
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
| #[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)] | ||
| #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] | ||
| pub enum Error { | ||
|
ndkazu marked this conversation as resolved.
|
||
| ArithmeticOverflow, | ||
|
ndkazu marked this conversation as resolved.
Outdated
|
||
| ProposalNotFound, | ||
| VotingPeriodEnded, | ||
| NotAMember, | ||
|
ndkazu marked this conversation as resolved.
Outdated
|
||
| AlreadyVoted, | ||
| VotingPeriodNotEnded, | ||
| ProposalAlreadyExecuted, | ||
|
ndkazu marked this conversation as resolved.
Outdated
|
||
| ProposalRejected, | ||
| Psp22(Psp22Error), | ||
| } | ||
|
|
||
| impl From<Psp22Error> for Error { | ||
| fn from(error: Psp22Error) -> Self { | ||
| Error::Psp22(error) | ||
|
ndkazu marked this conversation as resolved.
Outdated
|
||
| } | ||
| } | ||
|
|
||
| impl From<Error> for Psp22Error { | ||
|
ndkazu marked this conversation as resolved.
Outdated
|
||
| fn from(error: Error) -> Self { | ||
| match error { | ||
| Error::Psp22(psp22_error) => psp22_error, | ||
| _ => Psp22Error::Custom(String::from("Unknown error")), | ||
| } | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.