package govdao
import (
"std"
"strconv"
"gno.land/p/demo/ufmt"
pproposal "gno.land/p/gov/proposal"
)
var (
proposals = make([]*proposal, 0)
members = make([]std.Address, 0) // XXX: these should be pointers to avoid data duplication. Not possible due to VM bugs
)
const (
msgMissingExecutor = "missing proposal executor"
msgPropExecuted = "prop already executed"
msgPropExpired = "prop is expired"
msgPropInactive = "prop is not active anymore"
msgPropActive = "prop is still active"
msgPropNotAccepted = "prop is not accepted"
msgCallerNotAMember = "caller is not member of govdao"
msgProposalNotFound = "proposal not found"
)
func init() {
// Prepare the initial members
set := []std.Address{
/* GNO CORE */
"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj", // Jae
"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq", // Manfred
"g1e6gxg5tvc55mwsn7t7dymmlasratv7mkv0rap2", // Milos
"g1jazghxvvgz3egnr2fc8uf72z4g0l03596y9ls7", // Nemanja
"g17p2kkyy9lp2z3ecw4psssk357vxp20afnyl00d", // Petar
"g18amm3fc00t43dcxsys6udug0czyvqt9e7p23rd", // Marc
"g1dfr24yhk5ztwtqn2a36m8f6ud8cx5hww4dkjfl", // Antonio
"g19p3yzr3cuhzqa02j0ce6kzvyjqfzwemw3vam0x", // Guilhem
"g1mx4pum9976th863jgry4sdjzfwu03qan5w2v9j", // Ray
"g127l4gkhk0emwsx5tmxe96sp86c05h8vg5tufzq", // Maxwell
"g1acn3xssksatydd0fcuslvgmjyw0fzkjdhusddg", // Dylan
"g1cpx59z5r8vzeww2fm4ezpz7yvjs7kptywkm864", // Morgan
"g1ker4vvggvsyatexxn3hkthp2hu80pkhrwmuczr", // Sergio
"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5", // Leon
/* GNO DEVX */
"g16tfrrul20g4jzt3z303raqw8vs8s2pqqh5clwu", // Ilker
"g1hy6zry03hg5d8le9s2w4fxme6236hkgd928dun", // Jerónimo
"g15ruzptpql4dpuyzej0wkt5rq6r26kw4nxu9fwd", // Denis
"g1dnllrdzwfhxv3evyk09y48mgn5phfjvtyrlzm7", // Danny
"g1778y2yphxs2wpuaflsy5y9qwcd4gttn4g5yjx5", // Michelle
"g1mq7g0jszdmn4qdpc9tq94w0gyex37su892n80m", // Alan
"g197q5e9v00vuz256ly7fq7v3ekaun5cr7wmjgfh", // Salvo
"g1mpkp5lm8lwpm0pym4388836d009zfe4maxlqsq", // Alexis
/* ONBLOC */
"g17m8hlvm3k0agngz8vw29etpcjd2yvcel6pvt3k", // Andrew
"g12vx7dn3dqq89mz550zwunvg4qw6epq73d9csay", // Dongwon
"g1r04aw56fgvzy859fachr8hzzhqkulkaemltr76", // Blake
"g17n4y745s08awwq4e0a38lagsgtntna0749tnxe", // Jinwoo
"g1ckae7tc5sez8ul3ssne75sk4muwgttp6ks2ky9", // ByeongJun
/* TERITORI */
"g14u5eaheavy0ux4dmpykg2gvxpvqvexm9cyg58a", // Norman
/* BERTY */
"g1qynsu9dwj9lq0m5fkje7jh6qy3md80ztqnshhm", // Rémi
/* FLIPPANDO / ZENTASKTIC */
"g17ernafy6ctpcz6uepfsq2js8x2vz0wladh5yc3", // Dragos
}
// Save the members
members = append(members, set...)
}
type proposal struct {
author std.Address
comment string
executor pproposal.Executor
voter Voter
executed bool
voters []std.Address // XXX: these should be pointers to avoid data duplication. Not possible due to VM bugs.
}
func (p proposal) Status() Status {
if p.executor.IsExpired() {
return Expired
}
if p.executor.IsDone() {
return Succeeded
}
if !p.voter.IsFinished(members) {
return Active
}
if p.voter.IsAccepted(members) {
return Accepted
}
return NotAccepted
}
// Propose is designed to be called by another contract or with
// `maketx run`, not by a `maketx call`.
func Propose(comment string, executor pproposal.Executor) int {
// XXX: require payment?
if executor == nil {
panic(msgMissingExecutor)
}
caller := std.GetOrigCaller() // XXX: CHANGE THIS WHEN MSGRUN PERSIST CODE ESCAPING THE main() SCOPE! IT IS UNSAFE!
AssertIsMember(caller)
prop := &proposal{
comment: comment,
executor: executor,
author: caller,
voter: NewPercentageVoter(66), // at least 2/3 must say yes
}
proposals = append(proposals, prop)
return len(proposals) - 1
}
func VoteOnProposal(idx int, option string) {
assertProposalExists(idx)
caller := std.GetOrigCaller() // XXX: CHANGE THIS WHEN MSGRUN PERSIST CODE ESCAPING THE main() SCOPE! IT IS UNSAFE!
AssertIsMember(caller)
prop := getProposal(idx)
if prop.executed {
panic(msgPropExecuted)
}
if prop.executor.IsExpired() {
panic(msgPropExpired)
}
if prop.voter.IsFinished(members) {
panic(msgPropInactive)
}
prop.voter.Vote(members, caller, option)
}
func ExecuteProposal(idx int) {
assertProposalExists(idx)
prop := getProposal(idx)
if prop.executed {
panic(msgPropExecuted)
}
if prop.executor.IsExpired() {
panic(msgPropExpired)
}
if !prop.voter.IsFinished(members) {
panic(msgPropActive)
}
if !prop.voter.IsAccepted(members) {
panic(msgPropNotAccepted)
}
prop.executor.Execute()
prop.voters = members
prop.executed = true
}
func IsMember(addr std.Address) bool {
if len(members) == 0 { // special case for initial execution
return true
}
for _, v := range members {
if v == addr {
return true
}
}
return false
}
func AssertIsMember(addr std.Address) {
if !IsMember(addr) {
panic(msgCallerNotAMember)
}
}
func Render(path string) string {
if path == "" {
if len(proposals) == 0 {
return "No proposals found :(" // corner case
}
output := ""
for idx, prop := range proposals {
output += ufmt.Sprintf("- [%d](/r/gov/dao:%d) - %s (**%s**)(by %s)\n", idx, idx, prop.comment, string(prop.Status()), prop.author)
}
return output
}
// else display the proposal
idx, err := strconv.Atoi(path)
if err != nil {
return "404"
}
if !proposalExists(idx) {
return "404"
}
prop := getProposal(idx)
vs := members
if prop.executed {
vs = prop.voters
}
output := ""
output += ufmt.Sprintf("# Prop #%d", idx)
output += "\n\n"
output += prop.comment
output += "\n\n"
output += ufmt.Sprintf("Status: %s", string(prop.Status()))
output += "\n\n"
output += ufmt.Sprintf("Voting status: %s", prop.voter.Status(vs))
output += "\n\n"
output += ufmt.Sprintf("Author: %s", string(prop.author))
output += "\n\n"
return output
}
func getProposal(idx int) *proposal {
if idx > len(proposals)-1 {
panic(msgProposalNotFound)
}
return proposals[idx]
}
func proposalExists(idx int) bool {
return idx >= 0 && idx <= len(proposals)
}
func assertProposalExists(idx int) {
if !proposalExists(idx) {
panic("invalid proposal id")
}
}