Skip to content

Commit

Permalink
chore: add daodao pkg
Browse files Browse the repository at this point in the history
  • Loading branch information
hthieu1110 committed Aug 21, 2023
1 parent 5871be5 commit 56df907
Show file tree
Hide file tree
Showing 28 changed files with 2,323 additions and 0 deletions.
70 changes: 70 additions & 0 deletions examples/gno.land/p/demo/daodao/core_v3/dao_core.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package core

import (
"std"
"strings"

dao_interfaces "gno.land/p/demo/daodao/interfaces_v3"
"gno.land/p/demo/markdown_utils"
)

// TODO: add wrapper message handler to handle multiple proposal modules messages

type IDAOCore interface {
AddProposalModule(proposalMod dao_interfaces.IProposalModule)

VotingModule() dao_interfaces.IVotingModule
ProposalModules() []dao_interfaces.IProposalModule

Render(path string) string
}

type daoCore struct {
IDAOCore

votingModule dao_interfaces.IVotingModule
proposalModules []dao_interfaces.IProposalModule
}

func NewDAOCore(
votingModule dao_interfaces.IVotingModule,
proposalModules []dao_interfaces.IProposalModule,
) IDAOCore {
return &daoCore{
votingModule: votingModule,
proposalModules: proposalModules,
}
}

func (d *daoCore) VotingModule() dao_interfaces.IVotingModule {
return d.votingModule
}

func (d *daoCore) ProposalModules() []dao_interfaces.IProposalModule {
return d.proposalModules
}

func (d *daoCore) AddProposalModule(proposalMod dao_interfaces.IProposalModule) {
d.proposalModules = append(d.proposalModules, proposalMod)
}

func (d *daoCore) Render(path string) string {
s := "# DAO Core\n"
s += "This is a port of [DA0-DA0 contracts](https://github.com/DA0-DA0/dao-contracts)\n"
s += markdown_utils.Indent(d.votingModule.Render(path)) + "\n"
for _, propMod := range d.proposalModules {
s += markdown_utils.Indent(propMod.Render(path)) + "\n"
}
return s
}

func GetProposalModule(core IDAOCore, moduleIndex int) dao_interfaces.IProposalModule {
if moduleIndex < 0 {
panic("Module index must be >= 0")
}
mods := core.ProposalModules()
if moduleIndex >= len(mods) {
panic("invalid module index")
}
return mods[moduleIndex]
}
6 changes: 6 additions & 0 deletions examples/gno.land/p/demo/daodao/core_v3/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module gno.land/p/demo/daodao/core_v3

require (
"gno.land/p/demo/daodao/interfaces_v3" v0.0.0-latest
"gno.land/p/demo/markdown_utils" v0.0.0-latest
)
172 changes: 172 additions & 0 deletions examples/gno.land/p/demo/daodao/interfaces_v3/dao_interfaces.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package dao_interfaces

import (
"std"
"strconv"

"gno.land/p/demo/avl"
"gno.land/p/demo/jsonutil_v2"
)

type IVotingModule interface {
VotingPower(addr std.Address) uint64
TotalPower() uint64
Render(path string) string
}

type Ballot struct {
Power uint64
Vote Vote
Rationale string
}

func (b Ballot) ToJSON() string {
return jsonutil.FormatObject([]jsonutil.KeyValue{
{Key: "power", Value: b.Power},
{Key: "vote", Value: b.Vote},
{Key: "rationale", Value: b.Rationale},
})
}

type Votes struct {
Yes uint64
No uint64
Abstain uint64
}

func (v *Votes) Add(vote Vote, power uint64) {
switch vote {
case VoteYes:
v.Yes += power
case VoteNo:
v.No += power
case VoteAbstain:
v.Abstain += power
default:
panic("unknown vote kind")
}
}

func (v *Votes) Remove(vote Vote, power uint64) {
switch vote {
case VoteYes:
v.Yes -= power
case VoteNo:
v.No -= power
case VoteAbstain:
v.Abstain -= power
default:
panic("unknown vote kind")
}
}

func (v *Votes) Total() uint64 {
return v.Yes + v.No + v.Abstain
}

func (v Votes) ToJSON() string {
return jsonutil.FormatObject([]jsonutil.KeyValue{
{Key: "yes", Value: v.Yes},
{Key: "no", Value: v.No},
{Key: "abstain", Value: v.Abstain},
})
}

type Proposal struct {
ID int
Title string
Description string
Proposer std.Address
Messages []ExecutableMessage
Ballots *avl.Tree // dev
// Ballots *avl.MutTree // test3
Votes Votes
Status ProposalStatus
}

var _ jsonutil.JSONAble = (*Proposal)(nil)

func (p Proposal) ToJSON() string {
return jsonutil.FormatObject([]jsonutil.KeyValue{
{Key: "id", Value: p.ID},
{Key: "title", Value: p.Title},
{Key: "description", Value: p.Description},
{Key: "proposer", Value: p.Proposer},
{Key: "messages", Value: jsonutil.FormatSlice(p.Messages), Raw: true},
{Key: "ballots", Value: p.Ballots},
{Key: "votes", Value: p.Votes},
{Key: "status", Value: p.Status},
})
}

type ProposalStatus int

const (
ProposalStatusOpen ProposalStatus = iota
ProposalStatusPassed
ProposalStatusExecuted
)

func (p ProposalStatus) ToJSON() string {
return jsonutil.FormatString(p.String())
}

func (p ProposalStatus) String() string {
switch p {
case ProposalStatusOpen:
return "Open"
case ProposalStatusPassed:
return "Passed"
case ProposalStatusExecuted:
return "Executed"
default:
return "Unknown(" + strconv.Itoa(int(p)) + ")"
}
}

type Vote int

const (
VoteYes Vote = iota
VoteNo
VoteAbstain
)

func (v Vote) ToJSON() string {
return jsonutil.FormatString(v.String())
}

func (v Vote) String() string {
switch v {
case VoteYes:
return "Yes"
case VoteNo:
return "No"
case VoteAbstain:
return "Abstain"
default:
return "Unknown(" + strconv.Itoa(int(v)) + ")"
}
}

type IProposalModule interface {
Propose(
title string,
description string,
actions []ExecutableMessage,
)
Vote(proposalId int, vote Vote, rationale string)
Execute(proposalId int)
Threshold() Threshold

Proposals() []Proposal
GetBallot(proposalId int, addr std.Address) Ballot

Render(path string) string
}

type ExecutableMessage interface {
String() string
Binary() []byte
Type() string
}
66 changes: 66 additions & 0 deletions examples/gno.land/p/demo/daodao/interfaces_v3/dao_messages.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package dao_interfaces

import (
"encoding/base64"
"encoding/binary"
"strings"

"gno.land/p/demo/avl"
)

type MessageHandler interface {
Execute(message ExecutableMessage)
FromBinary(b []byte) ExecutableMessage
Type() string
}

type MessagesRegistry struct {
handlers *avl.Tree
}

func NewMessagesRegistry() *MessagesRegistry {
return &MessagesRegistry{handlers: avl.NewTree()}
}

func (r *MessagesRegistry) Register(handler MessageHandler) {
r.handlers.Set(handler.Type(), handler)
}

func (r *MessagesRegistry) FromBinary(b []byte) ExecutableMessage {
if len(b) < 2 {
panic("invalid ExecutableMessage: invalid length")
}
l := binary.BigEndian.Uint16(b[:2])
if len(b) < int(l+2) {
panic("invalid ExecutableMessage: invalid length")
}
t := string(b[2 : l+2])

h, ok := r.handlers.Get(t)
if !ok {
panic("invalid ExecutableMessage: invalid message type")
}
return h.(MessageHandler).FromBinary(b)
}

func (r *MessagesRegistry) FromBase64String(s string) ExecutableMessage {
b, err := base64.RawURLEncoding.DecodeString(s)
if err != nil {
panic("invalid ExecutableMessage: invalid base64 string")
}
return r.FromBinary(b)
}

func (r *MessagesRegistry) Execute(msg ExecutableMessage) {
h, ok := r.handlers.Get(msg.Type())
if !ok {
panic("invalid ExecutableMessage: invalid message type")
}
return h.(MessageHandler).Execute(msg)
}

func (r *MessagesRegistry) ExecuteMessages(msgs []ExecutableMessage) {
for _, msg := range msgs {
r.Execute(msg)
}
}
1 change: 1 addition & 0 deletions examples/gno.land/p/demo/daodao/interfaces_v3/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/p/demo/daodao/interfaces_v3
59 changes: 59 additions & 0 deletions examples/gno.land/p/demo/daodao/interfaces_v3/proposal_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package dao_interfaces

import (
"testing"

"gno.land/p/demo/avl"
"gno.land/p/demo/jsonutil_v2"
)

type NoopMessage struct{}

var _ ExecutableMessage = (*NoopMessage)(nil)

func (m NoopMessage) String() string {
return "noop"
}

func (m NoopMessage) Binary() []byte {
return nil
}

func (m NoopMessage) Type() string {
return "noop-type"
}

func (m NoopMessage) ToJSON() string {
return jsonutil.FormatString(m.String())
}

func TestProposalJSON(t *testing.T) {
props := []Proposal{
{
ID: 0,
Title: "Prop #0",
Description: "Wolol0\n\t\r",
Proposer: "0x1234567890",
Votes: Votes{
Yes: 7,
No: 21,
Abstain: 42,
},
Ballots: avl.NewTree(),
},
{
ID: 1,
Title: "Prop #1",
Description: `Wolol1\"`,
Proposer: "0x1234567890",
Status: ProposalStatusExecuted,
Messages: []ExecutableMessage{NoopMessage{}, NoopMessage{}, NoopMessage{}},
},
}
props[0].Ballots.Set("0x1234567890", Ballot{Power: 1, Vote: VoteYes, Rationale: "test"})
str := jsonutil.FormatSlice(props)
expected := `[{"id":0,"title":"Prop #0","description":"Wolol0\n\t\r","proposer":"0x1234567890","messages":[],"ballots":{"0x1234567890":{"power":1,"vote":"Yes","rationale":"test"}},"votes":{"yes":7,"no":21,"abstain":42},"status":"Open"},{"id":1,"title":"Prop #1","description":"Wolol1\\\"","proposer":"0x1234567890","messages":["noop","noop","noop"],"ballots":{},"votes":{"yes":0,"no":0,"abstain":0},"status":"Executed"}]`
if expected != str {
t.Fatalf("JSON does not match, expected %s, got %s", expected, str)
}
}
Loading

0 comments on commit 56df907

Please sign in to comment.