Skip to content

Commit

Permalink
feat(govdao): better rendering (gnolang#3096)
Browse files Browse the repository at this point in the history
## Description

Introduces better `gnoweb` rendering for the GovDAO suite, and better
help page actions for voting on proposals.

Home page before:
<img width="1535" alt="Screenshot 2024-11-08 at 16 29 49"
src="https://github.com/user-attachments/assets/d7eb9c5a-4195-45c6-b4ba-e57a57b95c21">

Home page after (also resolves usernames from `r/demo/users`):
<img width="1540" alt="Screenshot 2024-11-09 at 13 19 55"
src="https://github.com/user-attachments/assets/385e0727-8f2b-4451-b842-44a9c2870d6e">

Prop page before:
<img width="1547" alt="Screenshot 2024-11-08 at 16 30 43"
src="https://github.com/user-attachments/assets/1a936829-b9db-473e-b64e-bd5a992bffdb">

Prop page after:

<img width="1549" alt="Screenshot 2024-11-21 at 13 05 50"
src="https://github.com/user-attachments/assets/b75cefd5-ba84-4782-bdaa-910c23a29988">

The actions bar notifies the user when the proposal is no longer active
as well.

Continuation of gnolang#2579

<details><summary>Contributors' checklist...</summary>

- [x] Added new tests, or not needed, or not feasible
- [x] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [x] Updated the official documentation or not needed
- [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [x] Added references to related issues and PRs
- [x] Provided any useful hints for running manual tests
</details>
  • Loading branch information
leohhhn authored Dec 2, 2024
1 parent 2c06070 commit 7b7e758
Show file tree
Hide file tree
Showing 12 changed files with 485 additions and 208 deletions.
1 change: 1 addition & 0 deletions examples/gno.land/p/demo/dao/dao.gno
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const (
// that contains the necessary information to
// log and generate a valid proposal
type ProposalRequest struct {
Title string // the title associated with the proposal
Description string // the description associated with the proposal
Executor Executor // the proposal executor
}
Expand Down
5 changes: 4 additions & 1 deletion examples/gno.land/p/demo/dao/proposals.gno
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var (
Accepted ProposalStatus = "accepted" // proposal gathered quorum
NotAccepted ProposalStatus = "not accepted" // proposal failed to gather quorum
ExecutionSuccessful ProposalStatus = "execution successful" // proposal is executed successfully
ExecutionFailed ProposalStatus = "execution failed" // proposal is failed during execution
ExecutionFailed ProposalStatus = "execution failed" // proposal has failed during execution
)

func (s ProposalStatus) String() string {
Expand All @@ -42,6 +42,9 @@ type Proposal interface {
// Author returns the author of the proposal
Author() std.Address

// Title returns the title of the proposal
Title() string

// Description returns the description of the proposal
Description() string

Expand Down
8 changes: 8 additions & 0 deletions examples/gno.land/p/demo/simpledao/dao.gno
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package simpledao
import (
"errors"
"std"
"strings"

"gno.land/p/demo/avl"
"gno.land/p/demo/dao"
Expand All @@ -12,6 +13,7 @@ import (

var (
ErrInvalidExecutor = errors.New("invalid executor provided")
ErrInvalidTitle = errors.New("invalid proposal title provided")
ErrInsufficientProposalFunds = errors.New("insufficient funds for proposal")
ErrInsufficientExecuteFunds = errors.New("insufficient funds for executing proposal")
ErrProposalExecuted = errors.New("proposal already executed")
Expand Down Expand Up @@ -47,6 +49,11 @@ func (s *SimpleDAO) Propose(request dao.ProposalRequest) (uint64, error) {
return 0, ErrInvalidExecutor
}

// Make sure the title is set
if strings.TrimSpace(request.Title) == "" {
return 0, ErrInvalidTitle
}

var (
caller = getDAOCaller()
sentCoins = std.GetOrigSend() // Get the sent coins, if any
Expand All @@ -61,6 +68,7 @@ func (s *SimpleDAO) Propose(request dao.ProposalRequest) (uint64, error) {
// Create the wrapped proposal
prop := &proposal{
author: caller,
title: request.Title,
description: request.Description,
executor: request.Executor,
status: dao.Active,
Expand Down
49 changes: 49 additions & 0 deletions examples/gno.land/p/demo/simpledao/dao_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,50 @@ func TestSimpleDAO_Propose(t *testing.T) {
)
})

t.Run("invalid title", func(t *testing.T) {
t.Parallel()

var (
called = false
cb = func() error {
called = true

return nil
}
ex = &mockExecutor{
executeFn: cb,
}

sentCoins = std.NewCoins(
std.NewCoin(
"ugnot",
minProposalFeeValue,
),
)

ms = &mockMemberStore{
isMemberFn: func(_ std.Address) bool {
return false
},
}
s = New(ms)
)

std.TestSetOrigSend(sentCoins, std.Coins{})

_, err := s.Propose(dao.ProposalRequest{
Executor: ex,
Title: "", // Set invalid title
})
uassert.ErrorIs(
t,
err,
ErrInvalidTitle,
)

uassert.False(t, called)
})

t.Run("caller cannot cover fee", func(t *testing.T) {
t.Parallel()

Expand All @@ -58,6 +102,7 @@ func TestSimpleDAO_Propose(t *testing.T) {
ex = &mockExecutor{
executeFn: cb,
}
title = "Proposal title"

sentCoins = std.NewCoins(
std.NewCoin(
Expand All @@ -80,6 +125,7 @@ func TestSimpleDAO_Propose(t *testing.T) {

_, err := s.Propose(dao.ProposalRequest{
Executor: ex,
Title: title,
})
uassert.ErrorIs(
t,
Expand All @@ -105,6 +151,7 @@ func TestSimpleDAO_Propose(t *testing.T) {
executeFn: cb,
}
description = "Proposal description"
title = "Proposal title"

proposer = testutils.TestAddress("proposer")
sentCoins = std.NewCoins(
Expand All @@ -129,6 +176,7 @@ func TestSimpleDAO_Propose(t *testing.T) {

// Make sure the proposal was added
id, err := s.Propose(dao.ProposalRequest{
Title: title,
Description: description,
Executor: ex,
})
Expand All @@ -141,6 +189,7 @@ func TestSimpleDAO_Propose(t *testing.T) {

uassert.Equal(t, proposer.String(), prop.Author().String())
uassert.Equal(t, description, prop.Description())
uassert.Equal(t, title, prop.Title())
uassert.Equal(t, dao.Active.String(), prop.Status().String())

stats := prop.Stats()
Expand Down
38 changes: 26 additions & 12 deletions examples/gno.land/p/demo/simpledao/propstore.gno
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package simpledao
import (
"errors"
"std"
"strings"

"gno.land/p/demo/dao"
"gno.land/p/demo/seqid"
Expand All @@ -18,6 +19,7 @@ const maxRequestProposals = 10
// proposal is the internal simpledao proposal implementation
type proposal struct {
author std.Address // initiator of the proposal
title string // title of the proposal
description string // description of the proposal

executor dao.Executor // executor for the proposal
Expand All @@ -31,6 +33,10 @@ func (p *proposal) Author() std.Address {
return p.author
}

func (p *proposal) Title() string {
return p.title
}

func (p *proposal) Description() string {
return p.description
}
Expand Down Expand Up @@ -63,15 +69,20 @@ func (p *proposal) Render() string {
// Fetch the voting stats
stats := p.Stats()

output := ""
output += ufmt.Sprintf("Author: %s", p.Author().String())
output += "\n\n"
output += p.Description()
output += "\n\n"
output += ufmt.Sprintf("Status: %s", p.Status().String())
output += "\n\n"
output += ufmt.Sprintf(
"Voting stats: YES %d (%d%%), NO %d (%d%%), ABSTAIN %d (%d%%), MISSING VOTE %d (%d%%)",
var out string

out += "## Description\n\n"
if strings.TrimSpace(p.description) != "" {
out += ufmt.Sprintf("%s\n\n", p.description)
} else {
out += "No description provided.\n\n"
}

out += "## Proposal information\n\n"
out += ufmt.Sprintf("**Status: %s**\n\n", strings.ToUpper(p.Status().String()))

out += ufmt.Sprintf(
"**Voting stats:**\n- YES %d (%d%%)\n- NO %d (%d%%)\n- ABSTAIN %d (%d%%)\n- MISSING VOTES %d (%d%%)\n",
stats.YayVotes,
stats.YayPercent(),
stats.NayVotes,
Expand All @@ -81,10 +92,13 @@ func (p *proposal) Render() string {
stats.MissingVotes(),
stats.MissingVotesPercent(),
)
output += "\n\n"
output += ufmt.Sprintf("Threshold met: %t", stats.YayVotes > (2*stats.TotalVotingPower)/3)

return output
out += "\n\n"
thresholdOut := strings.ToUpper(ufmt.Sprintf("%t", stats.YayVotes > (2*stats.TotalVotingPower)/3))

out += ufmt.Sprintf("**Threshold met: %s**\n\n", thresholdOut)

return out
}

// addProposal adds a new simpledao proposal to the store
Expand Down
54 changes: 0 additions & 54 deletions examples/gno.land/r/gov/dao/v2/dao.gno
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ package govdao

import (
"std"
"strconv"

"gno.land/p/demo/dao"
"gno.land/p/demo/membstore"
"gno.land/p/demo/simpledao"
"gno.land/p/demo/ufmt"
)

var (
Expand Down Expand Up @@ -65,55 +63,3 @@ func GetPropStore() dao.PropStore {
func GetMembStore() membstore.MemberStore {
return members
}

func Render(path string) string {
if path == "" {
numProposals := d.Size()

if numProposals == 0 {
return "No proposals found :(" // corner case
}

output := ""

offset := uint64(0)
if numProposals >= 10 {
offset = uint64(numProposals) - 10
}

// Fetch the last 10 proposals
for idx, prop := range d.Proposals(offset, uint64(10)) {
output += ufmt.Sprintf(
"- [Proposal #%d](%s:%d) - (**%s**)(by %s)\n",
idx,
"/r/gov/dao/v2",
idx,
prop.Status().String(),
prop.Author().String(),
)
}

return output
}

// Display the detailed proposal
idx, err := strconv.Atoi(path)
if err != nil {
return "404: Invalid proposal ID"
}

// Fetch the proposal
prop, err := d.ProposalByID(uint64(idx))
if err != nil {
return ufmt.Sprintf("unable to fetch proposal, %s", err.Error())
}

// Render the proposal
output := ""
output += ufmt.Sprintf("# Prop #%d", idx)
output += "\n\n"
output += prop.Render()
output += "\n\n"

return output
}
2 changes: 2 additions & 0 deletions examples/gno.land/r/gov/dao/v2/gno.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ require (
gno.land/p/demo/simpledao v0.0.0-latest
gno.land/p/demo/ufmt v0.0.0-latest
gno.land/p/gov/executor v0.0.0-latest
gno.land/p/moul/txlink v0.0.0-latest
gno.land/r/demo/users v0.0.0-latest
)
Loading

0 comments on commit 7b7e758

Please sign in to comment.