PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/r/moul/x/daily/ballot/v0

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
v0
Namespace
moul / x / daily / ballot
Files
3 (README)(gnomod.toml)
Exported functions
7
Module
gno.land/r/moul/x/daily/ballot/v0
gno
0.9

Files (3)

  • README.mdmarkdown
  • gnomod.tomltoml
  • ballot.gnogno
ballot.gnogno
1// Package ballot is a Gno port of the classic Solidity "Ballot" voting2// contract from the Solidity docs. A chairperson seeds a fixed list of3// proposals and grants specific addresses the right to vote; each voter can4// either cast a direct vote or delegate their weight to another voter, and5// delegation follows a chain so a delegate who later delegates further still6// carries every accumulated weight. The winning proposal is whichever has7// the most accumulated weight at query time.8package ballot910import (11	"errors"12	"strconv"13	"strings"1415	"gno.land/p/nt/avl/v0"16)1718// proposal is one of the fixed choices set up at Init time.19type proposal struct {20	name  string21	votes uint6422}2324// voterInfo tracks one address's voting rights and delegation state.25type voterInfo struct {26	weight   uint64  // 0 means "no right to vote"27	voted    bool    // true once the weight has been cast (directly or delegated away)28	delegate address // set only when voted == true and this voter delegated29	vote     int     // proposal index this voter's weight ended up on, valid once tallied30}3132// Package-level persistent state.33var (34	chairperson address35	initialized bool36	proposals   []proposal37	voters      *avl.Tree // address string -> *voterInfo38)3940var (41	errAlreadyInit    = errors.New("ballot: already initialized")42	errNotInit        = errors.New("ballot: not initialized yet")43	errNotChairperson = errors.New("ballot: only the chairperson may do this")44	errNoProposals    = errors.New("ballot: at least one proposal name is required")45	errInvalidAddr    = errors.New("ballot: invalid address")46	errAlreadyRight   = errors.New("ballot: voter already has the right to vote")47	errAlreadyVoted   = errors.New("ballot: voter already voted")48	errNoRight        = errors.New("ballot: caller has no right to vote")49	errSelfDelegate   = errors.New("ballot: self-delegation is not allowed")50	errDelegateLoop   = errors.New("ballot: found a loop in the delegation chain")51	errDelegateNoRight = errors.New("ballot: delegate has no right to vote")52	errBadProposal    = errors.New("ballot: invalid proposal index")53)5455func init() {56	voters = avl.NewTree()57}5859func requireInit() {60	if !initialized {61		panic(errNotInit)62	}63}6465func getOrCreateVoter(addr address) *voterInfo {66	if v := voters.Get(addr.String()); v != nil {67		return v.(*voterInfo)68	}69	v := &voterInfo{}70	voters.Set(addr.String(), v)71	return v72}7374// Init configures the ballot exactly once: the caller becomes the75// chairperson (and is immediately given the right to vote), and76// proposalNamesCSV — a comma-separated list — seeds the fixed set of77// proposals. It panics if called twice or with no usable proposal names.78func Init(cur realm, proposalNamesCSV string) {79	if initialized {80		panic(errAlreadyInit)81	}8283	for _, raw := range strings.Split(proposalNamesCSV, ",") {84		name := strings.TrimSpace(raw)85		if name == "" {86			continue87		}88		proposals = append(proposals, proposal{name: name})89	}90	if len(proposals) == 0 {91		panic(errNoProposals)92	}9394	chairperson = cur.Previous().Address()95	voters.Set(chairperson.String(), &voterInfo{weight: 1})96	initialized = true97}9899// GiveRightToVote grants voterAddr a voting weight of 1. Only the100// chairperson may call this, and only for voters who haven't voted yet.101func GiveRightToVote(cur realm, voterAddr string) {102	requireInit()103	if cur.Previous().Address() != chairperson {104		panic(errNotChairperson)105	}106107	addr := address(voterAddr)108	if !addr.IsValid() {109		panic(errInvalidAddr)110	}111112	v := getOrCreateVoter(addr)113	if v.voted {114		panic(errAlreadyVoted)115	}116	if v.weight != 0 {117		panic(errAlreadyRight)118	}119	v.weight = 1120}121122// Delegate hands the caller's voting weight to toAddr. If toAddr has123// already voted, the weight is added directly to that proposal's tally;124// otherwise it accumulates on toAddr's own weight, ready to be cast (or125// delegated further) later. Mirrors Solidity's delegation-chain-following126// with a loop guard.127func Delegate(cur realm, toAddr string) {128	requireInit()129130	sender := cur.Previous().Address()131	from := getOrCreateVoter(sender)132	if from.weight == 0 {133		panic(errNoRight)134	}135	if from.voted {136		panic(errAlreadyVoted)137	}138139	to := address(toAddr)140	if !to.IsValid() {141		panic(errInvalidAddr)142	}143	if to == sender {144		panic(errSelfDelegate)145	}146147	var zero address148	maxSteps := voters.Size() + 1149	for steps := 0; ; steps++ {150		v := voters.Get(to.String())151		if v == nil {152			break153		}154		next := v.(*voterInfo).delegate155		if next == zero {156			break157		}158		to = next159		if to == sender {160			panic(errDelegateLoop)161		}162		if steps > maxSteps {163			panic(errDelegateLoop)164		}165	}166167	delegateVoter := getOrCreateVoter(to)168	if delegateVoter.weight == 0 {169		panic(errDelegateNoRight)170	}171172	from.voted = true173	from.delegate = to174175	if delegateVoter.voted {176		proposals[delegateVoter.vote].votes += from.weight177	} else {178		delegateVoter.weight += from.weight179	}180}181182// Vote casts the caller's full weight for proposals[proposalIndex].183func Vote(cur realm, proposalIndex int) {184	requireInit()185186	sender := cur.Previous().Address()187	v := getOrCreateVoter(sender)188	if v.weight == 0 {189		panic(errNoRight)190	}191	if v.voted {192		panic(errAlreadyVoted)193	}194	if proposalIndex < 0 || proposalIndex >= len(proposals) {195		panic(errBadProposal)196	}197198	v.voted = true199	v.vote = proposalIndex200	proposals[proposalIndex].votes += v.weight201}202203// WinningProposal returns the index of the proposal with the most204// accumulated weight, breaking ties in favor of the lowest index.205func WinningProposal() int {206	winner := 0207	best := uint64(0)208	for i, p := range proposals {209		if p.votes > best {210			best = p.votes211			winner = i212		}213	}214	return winner215}216217// WinnerName returns the name of the current winning proposal, or "" if218// the ballot hasn't been initialized.219func WinnerName() string {220	if len(proposals) == 0 {221		return ""222	}223	return proposals[WinningProposal()].name224}225226func Render(path string) string {227	var b strings.Builder228	b.WriteString("# Ballot\n\n")229230	if !initialized {231		b.WriteString("Not initialized yet. The first caller to run `Init` becomes the " +232			"chairperson and seeds the proposal list, e.g.:\n\n")233		b.WriteString("```\nInit(\"Alice,Bob,Carol\")\n```\n")234		return b.String()235	}236237	b.WriteString("Chairperson: `" + chairperson.String() + "`\n\n")238	b.WriteString("| # | Proposal | Votes |\n")239	b.WriteString("|---|---|---|\n")240	for i, p := range proposals {241		b.WriteString("| " + strconv.Itoa(i) + " | " + p.name + " | " +242			strconv.FormatUint(p.votes, 10) + " |\n")243	}244245	b.WriteString("\nCurrent winner: **" + WinnerName() + "** (index " +246		strconv.Itoa(WinningProposal()) + ")\n\n")247	b.WriteString("Registered voters: " + strconv.Itoa(voters.Size()) + "\n")248	return b.String()249}250

Functions

  • Delegate(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, toAddr string)

  • GiveRightToVote(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, voterAddr string)

  • Init(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, proposalNamesCSV string)

  • Render(path string) string

  • Vote(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, proposalIndex int)

  • WinnerName() string

  • WinningProposal() int

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

Rendered

RenderedRawgnoweb ↗

vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.