-
Notifications
You must be signed in to change notification settings - Fork 391
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
76d23ef
commit 8fa34a2
Showing
33 changed files
with
2,560 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
## Acknowledgment | ||
|
||
Based and adapted from: | ||
|
||
* https://www.openzeppelin.com/ |
77 changes: 77 additions & 0 deletions
77
examples/gno.land/p/demo/governance/checkpoints/checkpoints.gno
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package checkpoints | ||
|
||
import ( | ||
"std" | ||
) | ||
|
||
type Checkpoint struct { | ||
blockNumber int64 | ||
value uint64 | ||
} | ||
|
||
type History struct { | ||
checkpoints []Checkpoint | ||
} | ||
|
||
// Returns the value in the latest checkpoint, or zero if there are no checkpoints. | ||
func (history *History) Latest() uint64 { | ||
pos := len(history.checkpoints) | ||
if pos == 0 { | ||
return 0 | ||
} else { | ||
return history.checkpoints[pos-1].value | ||
} | ||
} | ||
|
||
// Returns the value at a given block number. If a checkpoint is not available at that block, the closest one | ||
// before it is returned, or zero otherwise. | ||
func (history *History) GetAtBlock(blockNumber int64) uint64 { | ||
height := std.GetHeight() | ||
if blockNumber >= height { | ||
panic("Checkpoints: block not yet mined") | ||
} | ||
var high int64 | ||
var low int64 | ||
|
||
high = int64(len(history.checkpoints)) | ||
low = 0 | ||
for low < high { | ||
mid := _average(low, high) | ||
if history.checkpoints[mid].blockNumber > blockNumber { | ||
high = mid | ||
} else { | ||
low = mid + 1 | ||
} | ||
} | ||
if high == 0 { | ||
return 0 | ||
} | ||
return history.checkpoints[high-1].value | ||
} | ||
|
||
// (a + b) / 2 can overflow. | ||
func _average(a, b int64) int64 { | ||
return (a & b) + (a^b)/2 | ||
} | ||
|
||
// Pushes a value onto a History so that it is stored as the checkpoint for the current block. | ||
// Returns previous value and new value. | ||
func (history *History) Push(value uint64) (uint64, uint64) { | ||
pos := len(history.checkpoints) | ||
old := history.Latest() | ||
height := std.GetHeight() | ||
// same block, update | ||
if pos > 0 && history.checkpoints[pos-1].blockNumber == height { | ||
history.checkpoints[pos-1].value = value | ||
} else { | ||
history.checkpoints = append(history.checkpoints, Checkpoint{blockNumber: height, value: value}) | ||
} | ||
return old, value | ||
} | ||
|
||
// Pushes a value onto a History, by updating the latest value using binary operation `op`. The new value will | ||
// be set to `op(latest, delta)`. | ||
// Returns previous value and new value. | ||
func (history *History) PushWithOp(op func(uint64, uint64) uint64, delta uint64) (uint64, uint64) { | ||
return history.Push(op(history.Latest(), delta)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package counters | ||
|
||
type Counter struct { | ||
value uint64 | ||
} | ||
|
||
func (counter *Counter) Current() uint64 { | ||
return counter.value | ||
} | ||
|
||
func (counter *Counter) Increment() { | ||
counter.value += 1 | ||
} | ||
|
||
func (counter *Counter) Decrement() { | ||
value := counter.value | ||
if value <= 0 { | ||
panic("Counter: decrement overflow") | ||
} | ||
counter.value = value - 1 | ||
} | ||
|
||
func (counter *Counter) Reset() { | ||
counter.value = 0 | ||
} |
164 changes: 164 additions & 0 deletions
164
examples/gno.land/p/demo/governance/governor/deposit.gno
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
package governor | ||
|
||
import std "std" | ||
import "gno.land/p/demo/avl" | ||
|
||
// update deposit for a proposal | ||
// return when active | ||
// burn when fail or not reaching quorum? NO | ||
|
||
// deposits only burned when veto | ||
func (gvr *Governor) Deposit(proposalId string) error { | ||
// std.AssertOriginCall() | ||
// caller is who deposit, will got returned deposit once succeeded | ||
caller := std.GetOrigCaller() | ||
// banker do the transfer work, for native coin | ||
banker := std.GetBanker(std.BankerTypeOrigSend) | ||
pkgaddr := std.GetOrigPkgAddr() | ||
println("pkgaddr: ", pkgaddr) | ||
// get deposit coin | ||
var coinD std.Coin | ||
sentCoins := std.GetOrigSend() | ||
if len(sentCoins) == 1 { | ||
coinD = sentCoins[0] | ||
} | ||
// check if return coin | ||
var proposal *ProposalCore | ||
proposalI, found := gvr.proposals.Get(proposalId) | ||
if !found { | ||
// return deposit | ||
banker.SendCoins(pkgaddr, caller, sentCoins) | ||
return ErrProposalNotExisit | ||
} else { | ||
proposal = proposalI.(*ProposalCore) | ||
} | ||
|
||
// check deposit state | ||
if d, ok := gvr.GetDeposit(proposalId); ok { | ||
if d.IsGTE(gvr.gs.minDeposit) { | ||
return ErrDepositEnded | ||
} | ||
} | ||
|
||
// if not return, send coins to governor pkg, coins is maintained in banker | ||
to := std.DerivePkgAddr("gno.land/p/demo/governance") | ||
banker.SendCoins(pkgaddr, to, sentCoins) | ||
|
||
// maintain deposits: proposalId <=> total | ||
total, ok := gvr.GetDeposit(proposalId) | ||
sum := total.Add(coinD) | ||
gvr.deposits.Set(proposalId, sum) | ||
|
||
// manipulate proposal state, via setting voteStart and voteEnd parameter | ||
var snapshot int64 | ||
// check if deposit amount reached | ||
// check threshold | ||
if sum.IsGTE(gvr.gs.minDeposit) { | ||
// ok to vote | ||
snapshot = std.GetHeight() | ||
} else { | ||
snapshot = std.GetHeight() + gvr.gs.getVotingDelay() | ||
} | ||
|
||
deadline := snapshot + gvr.gs.getVotingPeriod() | ||
proposal.voteStart.SetDeadline(snapshot) | ||
proposal.voteEnd.SetDeadline(deadline) | ||
|
||
// maintain depositList: proposalId=>(caller=>amount) | ||
var deposit *avl.Tree | ||
idt, ok := gvr.depositList.Get(proposalId) | ||
if !ok { | ||
// first time deposit for this proposalId | ||
deposit = avl.NewTree() | ||
} else { | ||
deposit = idt.(*avl.Tree) // caller => amount | ||
} | ||
// caller => amount sums | ||
amt := std.Coin{Amount: 0, Denom: "ugnot"} | ||
iamt, ok := deposit.Get(caller.String()) | ||
if ok { | ||
amt = iamt.(std.Coin) | ||
} | ||
sum = amt.Add(coinD) | ||
// update | ||
// deposit.Set(caller.String(), sum) | ||
deposit.Set(pkgaddr.String(), sum) | ||
gvr.depositList.Set(proposalId, deposit) | ||
|
||
return nil | ||
} | ||
|
||
// get deposit for a proposal | ||
func (gvr *Governor) GetDeposit(proposalId string) (std.Coin, bool) { | ||
d, ok := gvr.deposits.Get(proposalId) | ||
if !ok { | ||
return std.Coin{Amount: 0, Denom: "ugnot"}, false | ||
} | ||
deposit := d.(std.Coin) | ||
return deposit, true | ||
} | ||
|
||
// trigger | ||
func (gvr *Governor) Burn(proposalId string) { | ||
|
||
} | ||
|
||
// func (gvr *Governor) Refund(proposalId string, isBurn bool) error { | ||
func (gvr *Governor) unDeposit(proposalId string, isBurn bool) error { | ||
// banker do the transfer work, for native coin | ||
banker := std.GetBanker(std.BankerTypeRealmSend) | ||
vault := std.DerivePkgAddr("gno.land/p/demo/governance") | ||
println(banker.GetCoins(vault)) | ||
// iterate depositList, get caller, and amount, update value | ||
idt, ok := gvr.depositList.Get(proposalId) | ||
if !ok { | ||
return ErrProposalNotExisit | ||
} | ||
deposit := idt.(*avl.Tree) | ||
|
||
var toAddr std.Address | ||
deposit.Iterate("", "", func(n *avl.Node) bool { | ||
c := n.Key() | ||
a := n.Value().(std.Coin) | ||
println("caller: ", c) | ||
println("amount: ", a) | ||
deposit.Set(c, std.Coins{std.Coin{Denom: "ugnot", Amount: 0}}) | ||
if !isBurn { | ||
toAddr = std.Address(c) | ||
} else { | ||
toAddr = std.Address("") | ||
} | ||
// concrete send back coins | ||
banker.SendCoins(vault, toAddr, std.Coins{a}) | ||
return false | ||
}) | ||
|
||
// set every deposit to 0 | ||
gvr.depositList.Set(proposalId, deposit) | ||
|
||
// set total amount to 0 | ||
gvr.deposits.Set(proposalId, std.Coins{std.Coin{Denom: "ugnot", Amount: 0}}) | ||
return nil | ||
} | ||
|
||
// return coins when succeed, or burn when fail | ||
// fail in two ways: deposit not reach quorum, or 33.4% vote NOWithVeto | ||
func (gvr *Governor) Undeposit(proposalId string) error { | ||
|
||
// if passed, iterate depositList, return value, update deposits, and depositList | ||
// return deposit | ||
if gvr.state(proposalId) == Succeeded { | ||
gvr.unDeposit(proposalId, false) | ||
} | ||
// if failed, burn deposit, update deposits, and depositList | ||
// check NoWithVeto percentage, if exeeded 33.4% , burn, or if votes not reaching quorum, burn | ||
if gvr.state(proposalId) == QuorumNotReach { | ||
println("quorum not reach, going to burn") | ||
gvr.unDeposit(proposalId, true) | ||
} | ||
if gvr.state(proposalId) == Defeated { | ||
println("defeated, going to burn") | ||
gvr.unDeposit(proposalId, true) | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package governor | ||
|
||
import "errors" | ||
|
||
var ( | ||
ErrDepositEnded = errors.New("deposit ended") | ||
ErrProposalNotExisit = errors.New("proposal not exist") | ||
) |
106 changes: 106 additions & 0 deletions
106
examples/gno.land/p/demo/governance/governor/governor.gno
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package governor | ||
|
||
import ( | ||
"gno.land/p/demo/avl" | ||
"gno.land/p/demo/grc/exts/grc20votes" | ||
"gno.land/p/demo/ufmt" | ||
std "std" | ||
) | ||
|
||
type Governor struct { | ||
name string | ||
proposals *avl.Tree // ProposalId => ProposalCore | ||
proposal_ext *avl.Tree | ||
deposits *avl.Tree // proposalId <=> Amount, total amount | ||
depositList *avl.Tree // proposalId <=> (caller <=> amount) | ||
// cs *GovernorCountingSimple | ||
tally Tally | ||
gv *GovernorVotes | ||
gs *GovernorSettings | ||
} | ||
|
||
// TODO: default option | ||
|
||
func NewGovernor(name string, g20v *grc20votes.Grc20Votes, votingDelay int64, votingPeriod int64, minDeposit std.Coin, proposalThreshold int64, quorumNumerator uint64, vetoNumerator float64) *Governor { | ||
return &Governor{ | ||
name: name, | ||
proposals: avl.NewTree(), | ||
proposal_ext: avl.NewTree(), | ||
deposits: avl.NewTree(), | ||
depositList: avl.NewTree(), | ||
tally: NewGovernorCountingSimple(quorumNumerator, vetoNumerator, g20v), | ||
gv: NewGovernorVotes(g20v), | ||
gs: NewGovernorSettings(votingDelay, votingPeriod, minDeposit, proposalThreshold), | ||
} | ||
} | ||
|
||
func (gvr *Governor) getName() string { | ||
return gvr.name | ||
} | ||
|
||
func (gvr *Governor) getVersion() string { | ||
return "1" | ||
} | ||
|
||
type TallyResult struct { | ||
YesCount uint64 | ||
NoCount uint64 | ||
NoWithVetoCount uint64 | ||
AbstainCount uint64 | ||
} | ||
|
||
// return Tally result | ||
func (gvr *Governor) Tally(proposalId string) (state ProposalState, isBurnDeposit bool, tResult TallyResult) { | ||
state = gvr.state(proposalId) | ||
isBurnDeposit = gvr.tally.isBurnDeposit(proposalId) | ||
tResult = gvr.tally.getProposalVotes(proposalId) | ||
return | ||
} | ||
|
||
// Default additional encoded parameters used by castVote methods that don't include them | ||
// Note: Should be overriden by specific implementations to use an appropriate value, the | ||
// meaning of the additional params, in the context of that implementation | ||
func defaultParams() []byte { | ||
return []byte("") | ||
} | ||
|
||
// Returns weither `account` has cast a vote on `proposalId`. | ||
// func hasVoted(proposalId uint64, account std.Address) bool{} | ||
|
||
func (gvr *Governor) SetVotingDelay(vd int64) { | ||
gvr.gs.setVotingDelay(vd) | ||
} | ||
|
||
// Relays a transaction or function call to an arbitrary target. In cases where the governance executor | ||
// is some contract other than the governor itself, like when using a timelock, this function can be invoked | ||
// in a governance proposal to recover tokens or Ether that was sent to the governor contract by mistake. | ||
// Note that if the executor is simply the governor itself, use of `relay` is redundant. | ||
func relay(target std.Address, value uint64, data []byte) { | ||
// TODO: only governance | ||
// TODO: relay | ||
} | ||
|
||
// Address through which the governor executes action. Will be overloaded by module that execute actions | ||
// through another contract such as a timelock. | ||
func executor() std.Address { | ||
// TODO: return executor | ||
return "" | ||
} | ||
|
||
func (gvr *Governor) RenderHome(proposalId string) string { | ||
state := gvr.state(proposalId) | ||
snapShot := gvr.proposalSnapshot(proposalId) | ||
deadLine := gvr.proposalDeadline(proposalId) | ||
totalDeposit := std.Coin{Amount: 0, Denom: "ugnot"} | ||
itotal, ok := gvr.deposits.Get(proposalId) | ||
if ok { | ||
totalDeposit = itotal.(std.Coin) | ||
} | ||
|
||
str := "" | ||
str += ufmt.Sprintf("* **Proposal state is***: %s\n", state.String()) | ||
str += ufmt.Sprintf("* **Proposal snapShot is***: %d\n", snapShot) | ||
str += ufmt.Sprintf("* **Proposal deadLine is***: %d\n", deadLine) | ||
str += ufmt.Sprintf("* **Proposal deposit is***: %s\n", totalDeposit.String()) | ||
return str | ||
} |
Oops, something went wrong.