Skip to content

Commit

Permalink
Abstain votes
Browse files Browse the repository at this point in the history
  • Loading branch information
0xkorin committed Dec 25, 2023
1 parent c74c452 commit 4527903
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 28 deletions.
38 changes: 28 additions & 10 deletions contracts/governance/GenericGovernor.vy
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ struct Proposal:
hash: bytes32
yea: uint256
nay: uint256
abstain: uint256

genesis: public(immutable(uint256))

Expand Down Expand Up @@ -62,6 +63,7 @@ event Vote:
idx: indexed(uint256)
yea: uint256
nay: uint256
abstain: uint256

event Enact:
idx: indexed(uint256)
Expand Down Expand Up @@ -339,9 +341,12 @@ def _proposal_state(_idx: uint256) -> uint256:
if current_epoch == vote_epoch + 1:
yea: uint256 = self.proposals[_idx].yea
nay: uint256 = self.proposals[_idx].nay
votes: uint256 = yea + nay
if votes > 0 and votes >= self._quorum(vote_epoch) and \
yea * VOTE_SCALE >= votes * self._majority(vote_epoch):
abstain: uint256 = self.proposals[_idx].abstain

counted: uint256 = yea + nay # for majority purposes
total: uint256 = counted + abstain # for quorum purposes
if counted > 0 and total >= self._quorum(vote_epoch) and \
yea * VOTE_SCALE >= counted * self._majority(vote_epoch):
return STATE_PASSED

return STATE_REJECTED
Expand Down Expand Up @@ -396,36 +401,45 @@ def vote_yea(_idx: uint256):
@notice Vote in favor of a proposal
@param _idx Proposal index
"""
self._vote(_idx, VOTE_SCALE, 0)
self._vote(_idx, VOTE_SCALE, 0, 0)

@external
def vote_nay(_idx: uint256):
"""
@notice Vote in opposition of a proposal
@param _idx Proposal index
"""
self._vote(_idx, 0, VOTE_SCALE)
self._vote(_idx, 0, VOTE_SCALE, 0)

@external
def vote_abstain(_idx: uint256):
"""
@notice Vote in abstention of a proposal
@param _idx Proposal index
"""
self._vote(_idx, 0, 0, VOTE_SCALE)

@external
def vote(_idx: uint256, _yea: uint256, _nay: uint256):
def vote(_idx: uint256, _yea: uint256, _nay: uint256, _abstain: uint256):
"""
@notice Weighted vote on a proposal
@param _idx Proposal index
@param _yea Fraction of votes in favor
@param _nay Fraction of votes in opposition
@param _abstain Fraction of abstained votes
"""
self._vote(_idx, _yea, _nay)
self._vote(_idx, _yea, _nay, _abstain)

@internal
def _vote(_idx: uint256, _yea: uint256, _nay: uint256):
def _vote(_idx: uint256, _yea: uint256, _nay: uint256, _abstain: uint256):
"""
@notice Weighted vote on a proposal
"""
assert self._vote_open()
assert self.proposals[_idx].epoch == self._epoch()
assert self.proposals[_idx].state == STATE_PROPOSED
assert not self.voted[msg.sender][_idx]
assert _yea + _nay == VOTE_SCALE
assert _yea + _nay + _abstain == VOTE_SCALE

weight: uint256 = Measure(self.measure).vote_weight(msg.sender)
assert weight > 0
Expand All @@ -438,7 +452,11 @@ def _vote(_idx: uint256, _yea: uint256, _nay: uint256):
if _nay > 0:
nay = weight * _nay / VOTE_SCALE
self.proposals[_idx].nay += nay
log Vote(msg.sender, _idx, yea, nay)
abstain: uint256 = 0
if _abstain > 0:
abstain = weight * _abstain / VOTE_SCALE
self.proposals[_idx].abstain += abstain
log Vote(msg.sender, _idx, yea, nay, abstain)

@external
def enact(_idx: uint256, _script: Bytes[65536]):
Expand Down
109 changes: 91 additions & 18 deletions tests/governance/test_generic_governor.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,33 @@ def test_vote_nay(chain, alice, bob, measure, governor, script):
governor.vote_nay(idx, sender=bob)
assert governor.proposal(idx).nay == 3 * UNIT

def test_vote_abstain(chain, alice, bob, measure, governor, script):
assert governor.propose_open()
assert not governor.vote_open()
idx = governor.propose(script, sender=alice).return_value
chain.pending_timestamp += VOTE_START
chain.mine()
assert governor.vote_open()

# no voting power
with ape.reverts():
governor.vote_abstain(idx, sender=alice)

measure.set_vote_weight(alice, UNIT, sender=alice)
assert not governor.voted(alice, idx)
governor.vote_abstain(idx, sender=alice)
assert governor.voted(alice, idx)
assert governor.proposal(idx).abstain == UNIT

# no double votes
with ape.reverts():
governor.vote_abstain(idx, sender=alice)

# votes are added
measure.set_vote_weight(bob, 2 * UNIT, sender=alice)
governor.vote_abstain(idx, sender=bob)
assert governor.proposal(idx).abstain == 3 * UNIT

def test_vote(chain, alice, bob, measure, governor, script):
assert governor.propose_open()
assert not governor.vote_open()
Expand All @@ -158,29 +185,35 @@ def test_vote(chain, alice, bob, measure, governor, script):

# no voting power
with ape.reverts():
governor.vote(idx, 4000, 6000, sender=alice)
governor.vote(idx, 4000, 6000, 0, sender=alice)

measure.set_vote_weight(alice, 10 * UNIT, sender=alice)

# votes dont add up
with ape.reverts():
governor.vote(idx, 6000, 6000, sender=alice)
governor.vote(idx, 1000, 1000, 1000, sender=alice)
with ape.reverts():
governor.vote(idx, 6000, 6000, 0, sender=alice)
with ape.reverts():
governor.vote(idx, 6000, 4000, 1000, sender=alice)

assert not governor.voted(alice, idx)
governor.vote(idx, 4000, 6000, sender=alice)
governor.vote(idx, 4000, 5000, 1000, sender=alice)
assert governor.voted(alice, idx)
assert governor.proposal(idx).yea == 4 * UNIT
assert governor.proposal(idx).nay == 6 * UNIT
assert governor.proposal(idx).nay == 5 * UNIT
assert governor.proposal(idx).abstain == UNIT

# no double votes
with ape.reverts():
governor.vote(idx, 6000, 4000, sender=alice)
governor.vote(idx, 6000, 4000, 0, sender=alice)

# votes are added
measure.set_vote_weight(bob, 20 * UNIT, sender=alice)
governor.vote(idx, 5000, 5000, sender=bob)
assert governor.proposal(idx).yea == 14 * UNIT
assert governor.proposal(idx).nay == 16 * UNIT
governor.vote(idx, 4000, 4000, 2000, sender=bob)
assert governor.proposal(idx).yea == 12 * UNIT
assert governor.proposal(idx).nay == 13 * UNIT
assert governor.proposal(idx).abstain == 5 * UNIT

def test_vote_retracted(chain, alice, measure, governor, script):
assert governor.propose_open()
Expand Down Expand Up @@ -210,10 +243,19 @@ def test_vote_closed_no_votes(chain, alice, governor, script):
chain.mine()
assert governor.proposal_state(idx) == STATE_REJECTED

def test_vote_closed_yea(chain, alice, bob, measure, governor, script):
def test_vote_closed_no_counted_votes(chain, alice, measure, governor, script):
idx = governor.propose(script, sender=alice).return_value
chain.pending_timestamp += VOTE_START
measure.set_vote_weight(alice, UNIT, sender=alice)
governor.vote_abstain(idx, sender=alice)

chain.pending_timestamp += WEEK
chain.mine()
assert governor.proposal_state(idx) == STATE_REJECTED

def test_vote_closed_yea(chain, alice, bob, measure, governor, script):
idx = governor.propose(script, sender=alice).return_value
chain.pending_timestamp += VOTE_START
measure.set_vote_weight(alice, 2 * UNIT, sender=alice)
governor.vote_yea(idx, sender=alice)
measure.set_vote_weight(bob, UNIT, sender=alice)
Expand All @@ -228,10 +270,21 @@ def test_vote_closed_yea(chain, alice, bob, measure, governor, script):
chain.mine()
assert governor.proposal_state(idx) == STATE_REJECTED

def test_vote_closed_nay(chain, alice, bob, measure, governor, script):
def test_vote_closed_yea_abstain(chain, alice, bob, measure, governor, script):
idx = governor.propose(script, sender=alice).return_value
chain.pending_timestamp += VOTE_START
measure.set_vote_weight(alice, UNIT, sender=alice)
governor.vote_yea(idx, sender=alice)
measure.set_vote_weight(bob, 4 * UNIT, sender=alice)
governor.vote_abstain(idx, sender=bob)

chain.pending_timestamp += WEEK
chain.mine()
assert governor.proposal_state(idx) == STATE_PASSED

def test_vote_closed_nay(chain, alice, bob, measure, governor, script):
idx = governor.propose(script, sender=alice).return_value
chain.pending_timestamp += VOTE_START
measure.set_vote_weight(alice, 2 * UNIT, sender=alice)
governor.vote_nay(idx, sender=alice)
measure.set_vote_weight(bob, UNIT, sender=alice)
Expand All @@ -241,6 +294,18 @@ def test_vote_closed_nay(chain, alice, bob, measure, governor, script):
chain.mine()
assert governor.proposal_state(idx) == STATE_REJECTED

def test_vote_closed_nay_abstain(chain, alice, bob, measure, governor, script):
idx = governor.propose(script, sender=alice).return_value
chain.pending_timestamp += VOTE_START
measure.set_vote_weight(alice, UNIT, sender=alice)
governor.vote_nay(idx, sender=alice)
measure.set_vote_weight(bob, 4 * UNIT, sender=alice)
governor.vote_abstain(idx, sender=bob)

chain.pending_timestamp += WEEK
chain.mine()
assert governor.proposal_state(idx) == STATE_REJECTED

def test_vote_closed_supermajority_yea(chain, deployer, alice, measure, governor, script):
# majority needs to be at least 50%
with ape.reverts():
Expand All @@ -253,9 +318,8 @@ def test_vote_closed_supermajority_yea(chain, deployer, alice, measure, governor

idx = governor.propose(script, sender=alice).return_value
chain.pending_timestamp += VOTE_START
chain.mine()
measure.set_vote_weight(alice, UNIT, sender=alice)
governor.vote(idx, 7000, 3000, sender=alice)
governor.vote(idx, 7000, 3000, 0, sender=alice)

chain.pending_timestamp += WEEK
chain.mine()
Expand All @@ -265,9 +329,8 @@ def test_vote_closed_supermajority_nay(chain, deployer, alice, measure, governor
governor.set_majority(6666, sender=deployer)
idx = governor.propose(script, sender=alice).return_value
chain.pending_timestamp += VOTE_START
chain.mine()
measure.set_vote_weight(alice, UNIT, sender=alice)
governor.vote(idx, 6000, 4000, sender=alice)
governor.vote(idx, 6000, 4000, 0, sender=alice)

chain.pending_timestamp += WEEK
chain.mine()
Expand All @@ -281,19 +344,30 @@ def test_vote_closed_quorum(chain, deployer, alice, measure, governor, script):

idx = governor.propose(script, sender=alice).return_value
chain.pending_timestamp += VOTE_START
chain.mine()
measure.set_vote_weight(alice, 2 * UNIT, sender=alice)
governor.vote_yea(idx, sender=alice)

chain.pending_timestamp += WEEK
chain.mine()
assert governor.proposal_state(idx) == STATE_PASSED

def test_vote_closed_no_quorum(chain, deployer, alice, measure, governor, script):
def test_vote_closed_quorum_abstain(chain, deployer, alice, bob, measure, governor, script):
governor.set_quorum(2 * UNIT, sender=deployer)
idx = governor.propose(script, sender=alice).return_value
chain.pending_timestamp += VOTE_START
measure.set_vote_weight(alice, UNIT, sender=alice)
measure.set_vote_weight(bob, UNIT, sender=bob)
governor.vote_yea(idx, sender=alice)
governor.vote_abstain(idx, sender=bob)

chain.pending_timestamp += WEEK
chain.mine()
assert governor.proposal_state(idx) == STATE_PASSED

def test_vote_closed_no_quorum(chain, deployer, alice, measure, governor, script):
governor.set_quorum(2 * UNIT, sender=deployer)
idx = governor.propose(script, sender=alice).return_value
chain.pending_timestamp += VOTE_START
measure.set_vote_weight(alice, UNIT, sender=alice)
governor.vote_yea(idx, sender=alice)

Expand Down Expand Up @@ -345,7 +419,6 @@ def test_execute_delay(chain, deployer, alice, bob, measure, token, governor, sc

idx = governor.propose(script, sender=alice).return_value
chain.pending_timestamp += VOTE_START
chain.mine()
measure.set_vote_weight(alice, UNIT, sender=alice)
governor.vote_yea(idx, sender=alice)

Expand Down Expand Up @@ -448,7 +521,7 @@ def test_ordering(chain, deployer, alice, measure, token, proxy, executor, gover

chain.pending_timestamp += VOTE_START
measure.set_vote_weight(alice, UNIT, sender=alice)
governor.vote(idx1, 5000, 5000, sender=alice)
governor.vote(idx1, 5000, 5000, 0, sender=alice)
governor.vote_yea(idx2, sender=alice)
chain.pending_timestamp += WEEK

Expand Down

0 comments on commit 4527903

Please sign in to comment.