-
Notifications
You must be signed in to change notification settings - Fork 443
feat(govdao): add proposal fee-based for non-member #4944
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
base: master
Are you sure you want to change the base?
Changes from 7 commits
70e6a10
25601e0
6c95ed3
ea1804b
19a819a
640b24f
a75dd70
1dcd085
44d0a1b
75ca012
3ac4368
856c91e
2d60019
9d35062
9ce647e
afbb935
3334b17
288623b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ package impl | |
|
|
||
| import ( | ||
| "chain" | ||
| "chain/banker" | ||
| "chain/runtime" | ||
| "errors" | ||
|
|
||
|
|
@@ -13,6 +14,9 @@ import ( | |
|
|
||
| var ErrMemberNotFound = errors.New("member not found") | ||
|
|
||
| // ProposalFeeAmount is the fee required for non-members to create proposals (in ugnot) | ||
| const ProposalFeeAmount int64 = 1000000 // 1 GNOT | ||
|
||
|
|
||
| type GovDAO struct { | ||
| pss ProposalsStatuses | ||
| render *render | ||
|
|
@@ -64,11 +68,19 @@ func (g *GovDAO) PreCreateProposal(r dao.ProposalRequest) (address, error) { | |
| runtime.CurrentRealm(), runtime.PreviousRealm())) | ||
| } | ||
|
|
||
| // Verify that the one creating the proposal is a member. | ||
| caller := runtime.OriginCaller() | ||
| mem, _ := getMembers(cross).GetMember(caller) | ||
|
|
||
| // If the caller is not a member, they must pay a fee | ||
| if mem == nil { | ||
| return caller, errors.New("only members can create new proposals") | ||
| sent := banker.OriginSend() | ||
| feeRequired := chain.NewCoin("ugnot", ProposalFeeAmount) | ||
| if len(sent) == 0 || sent.AmountOf("ugnot") < ProposalFeeAmount { | ||
| return caller, errors.New(ufmt.Sprintf( | ||
| "non-members must send %s to create a proposal", | ||
| feeRequired.String(), | ||
| )) | ||
| } | ||
| } | ||
|
|
||
| return caller, nil | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,10 @@ import ( | |
| "strings" | ||
| "testing" | ||
|
|
||
| "chain" | ||
|
|
||
| "gno.land/p/nt/testutils" | ||
| "gno.land/p/nt/uassert" | ||
| "gno.land/p/nt/urequire" | ||
| "gno.land/r/gov/dao" | ||
| "gno.land/r/gov/dao/v3/memberstore" | ||
|
|
@@ -87,7 +90,7 @@ func TestCreateProposalAndVote(cur realm, t *testing.T) { | |
| dao.MustCreateProposal(cross, NewAddMemberRequest(cur, nm1, memberstore.T3, portfolio)) | ||
| }) | ||
|
|
||
| urequire.AbortsWithMessage(t, "proposer is not a member", func(cur realm) { | ||
| uassert.AbortsContains(t, "proposal creation must be done directly by a user or through the r/gov/dao proxy", func(cur realm) { | ||
| dao.MustCreateProposal(cross, NewAddMemberRequest(cur, nm1, memberstore.T2, portfolio)) | ||
| }) | ||
|
|
||
|
|
@@ -216,9 +219,8 @@ func TestUpgradeDaoImplementation(t *testing.T) { | |
| testing.SetOriginCaller(noMember) | ||
| testing.SetRealm(testing.NewCodeRealm("gno.land/r/gov/dao/v3/impl")) | ||
|
|
||
| urequire.PanicsWithMessage(t, "proposer is not a member", func() { | ||
| NewUpgradeDaoImplRequest(govDAO, "gno.land/r/gov/dao/v4/impl", "Something happened and we have to fix it.") | ||
| }) | ||
| testing.SetOriginSend(chain.Coins{chain.Coin{Denom: "ugnot", Amount: ProposalFeeAmount}}) // 1 GNOT | ||
| NewUpgradeDaoImplRequest(govDAO, "gno.land/r/gov/dao/v4/impl", "Something happened and we have to fix it.") | ||
|
|
||
| testing.SetOriginCaller(m1) | ||
| testing.SetRealm(testing.NewCodeRealm("gno.land/r/gov/dao/v3/impl")) | ||
|
|
@@ -275,6 +277,42 @@ func TestUpgradeDaoImplementation(t *testing.T) { | |
| urequire.Equal(t, true, contains(dao.Render("7"), "YES PERCENT: 68.42105263157895%")) | ||
| } | ||
|
|
||
| func TestNonMemberProposalFee(cur realm, t *testing.T) { | ||
| loadMembers() | ||
|
|
||
| portfolio := "# This is my portfolio:\n\n- THINGS" | ||
| nm1 := testutils.TestAddress("nm1") | ||
|
|
||
| testing.SetOriginCaller(noMember) | ||
| testing.SetRealm(testing.NewCodeRealm("gno.land/r/gov/dao/v3/impl")) | ||
|
|
||
| // Test: Non-member without fee should fail with realm validation error | ||
| uassert.AbortsContains(t, "proposal creation must be done directly by a user or through the r/gov/dao proxy", func(cur realm) { | ||
| dao.MustCreateProposal(cross, NewAddMemberRequest(cur, nm1, memberstore.T2, portfolio)) | ||
| }) | ||
|
|
||
| // Test: Non-member with insufficient fee should also fail with realm validation error | ||
| testing.SetOriginSend(chain.Coins{chain.Coin{Denom: "ugnot", Amount: 500000}}) // 0.5 GNOT - insufficient | ||
| uassert.AbortsContains(t, "proposal creation must be done directly by a user or through the r/gov/dao proxy", func(cur realm) { | ||
|
||
| dao.MustCreateProposal(cross, NewAddMemberRequest(cur, nm1, memberstore.T2, portfolio)) | ||
| }) | ||
|
|
||
| // Test: Non-member with correct fee should also fail with realm validation error | ||
| testing.SetOriginSend(chain.Coins{chain.Coin{Denom: "ugnot", Amount: ProposalFeeAmount}}) // 1 GNOT | ||
| uassert.AbortsContains(t, "proposal creation must be done directly by a user or through the r/gov/dao proxy", func(cur realm) { | ||
| dao.MustCreateProposal(cross, NewAddMemberRequest(cur, nm1, memberstore.T2, portfolio)) | ||
| }) | ||
|
|
||
| // Reset OrigSend | ||
| testing.SetOriginSend(chain.Coins{}) | ||
|
|
||
| // Test: Members also fail due to realm validation in tests | ||
| testing.SetOriginCaller(m1) | ||
| uassert.AbortsContains(t, "proposal creation must be done directly by a user or through the r/gov/dao proxy", func(cur realm) { | ||
| dao.MustCreateProposal(cross, NewAddMemberRequest(cur, testutils.TestAddress("nm2"), memberstore.T2, portfolio)) | ||
| }) | ||
| } | ||
|
|
||
| func contains(s, substr string) bool { | ||
| return strings.Index(s, substr) >= 0 | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,6 @@ | ||
| package impl | ||
|
|
||
| import ( | ||
| "chain/runtime" | ||
|
|
||
| "gno.land/p/aeddi/panictoerr" | ||
| "gno.land/p/moul/md" | ||
| trs_pkg "gno.land/p/nt/treasury" | ||
|
|
@@ -14,11 +12,6 @@ import ( | |
| ) | ||
|
|
||
| func NewChangeLawRequest(_ realm, newLaw *Law) dao.ProposalRequest { | ||
| member, _ := memberstore.Get().GetMember(runtime.OriginCaller()) | ||
| if member == nil { | ||
| panic("proposer is not a member") | ||
| } | ||
|
|
||
| cb := func(_ realm) error { | ||
| law = newLaw | ||
| return nil | ||
|
|
@@ -30,11 +23,6 @@ func NewChangeLawRequest(_ realm, newLaw *Law) dao.ProposalRequest { | |
| } | ||
|
|
||
| func NewUpgradeDaoImplRequest(newDao dao.DAO, realmPkg, reason string) dao.ProposalRequest { | ||
| member, _ := memberstore.Get().GetMember(runtime.OriginCaller()) | ||
| if member == nil { | ||
| panic("proposer is not a member") | ||
| } | ||
|
|
||
| cb := func(_ realm) error { | ||
| // dao.UpdateImpl() must be cross-called from v3/impl but | ||
| // what calls this cb function is r/gov/dao. | ||
|
|
@@ -66,17 +54,7 @@ func NewAddMemberRequest(_ realm, addr address, tier string, portfolio string) d | |
| panic("A portfolio for the proposed member is required") | ||
| } | ||
|
|
||
| member, _ := memberstore.Get().GetMember(runtime.OriginCaller()) | ||
| if member == nil { | ||
| panic("proposer is not a member") | ||
| } | ||
|
|
||
| if member.InvitationPoints <= 0 { | ||
| panic("proposer does not have enough invitation points for inviting new people to the board") | ||
| } | ||
|
Comment on lines
-74
to
-76
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for me this line should never have been there, invitations point should be only for the T3 AddMember function but since it was here it makes me wondering if we should keep it in case the proposal is coming from a member ?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, in the end this is just a helper function to create a |
||
|
|
||
| cb := func(_ realm) error { | ||
| member.RemoveInvitationPoint() | ||
| err := memberstore.Get().SetMember(tier, addr, memberByTier(tier)) | ||
|
|
||
| return err | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package impl | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "gno.land/p/nt/urequire" | ||
| ) | ||
|
|
||
| // TestRenderMembershipBadge verifies that the non-member badge is displayed correctly | ||
| func TestRenderMembershipBadge(t *testing.T) { | ||
| loadMembers() | ||
| dao := NewGovDAO() | ||
|
|
||
| // Test member (should not show badge) | ||
| badge := renderMembershipBadge(m1, dao) | ||
| urequire.Equal(t, "", badge, "Members should not have a badge") | ||
|
|
||
| // Test non-member (should show badge) | ||
| badge = renderMembershipBadge(noMember, dao) | ||
| urequire.Equal(t, "`[non-member]`", badge, "Non-members should have a badge") | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.