Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add non delegable flag feature #9

Merged
merged 22 commits into from
Dec 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d6c1343
feat: add wonderGovernor and wonderVotes contract examples
0xRaccoon Dec 20, 2023
b4410f1
fix: return memory proposal instead of storage
0xRaccoon Dec 20, 2023
120a7f7
Merge branch 'dev' of github-defi:defi-wonderland/wonderland-governoo…
0xRaccoon Dec 21, 2023
d7300bc
test: add Governor unit tests
0xRaccoon Dec 21, 2023
d07cb6c
test: add governor unit tests
0xRaccoon Dec 21, 2023
3a3a65d
test: add governor castVote tests
0xRaccoon Dec 21, 2023
6414e1a
test: add governor unit tests
0xRaccoon Dec 21, 2023
681175b
test: add wonder votes unit tests
0xRaccoon Dec 22, 2023
71d2242
test: add wonder votes unit tests
0xRaccoon Dec 22, 2023
3856d2d
test: add wonder votes unit tests
0xRaccoon Dec 22, 2023
fbeccaf
test: add integration tests
0xRaccoon Dec 26, 2023
ef0afb9
fix: add optimism rpc
0xRaccoon Dec 26, 2023
d8440ad
fix: add optimism rpc
0xRaccoon Dec 26, 2023
66a61f2
test: add integration tests
0xRaccoon Dec 27, 2023
8f0f9b0
fix: add missing file headers
0xRaccoon Dec 27, 2023
991e7e8
Merge branch 'test/add-governor-unit-tests' of github-defi:defi-wonde…
0xRaccoon Dec 27, 2023
278f18a
fix: add missing headers
0xRaccoon Dec 27, 2023
0320deb
Merge branch 'test/add-wonder-votes-unit-tests' of github-defi:defi-w…
0xRaccoon Dec 27, 2023
5a3bbf8
feat: add non delegable flag feature
0xRaccoon Dec 28, 2023
6490b89
feat: add non-delegable flag feature
0xRaccoon Dec 28, 2023
7e41901
Merge branch 'dev' of github-defi:defi-wonderland/wonderland-governoo…
0xRaccoon Dec 28, 2023
21250cb
test: add delegation suspension integration tests
0xRaccoon Dec 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ broadcast/*/*/*

# Out dir
out

# Coverage
lcov.info

61 changes: 51 additions & 10 deletions solidity/contracts/governance/utils/WonderVotes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes

mapping(uint8 proposalType => Checkpoints.Trace208) private _totalCheckpoints;

mapping(address account => bool) private _nonDelegableAddresses;

/**
* @dev The clock was incorrectly modified.
*/
Expand Down Expand Up @@ -132,15 +134,21 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes
/**
* @dev Delegates votes from the sender to `delegatee`.
*/
function delegate(Delegate[] calldata delegatees, uint8 proposalType) public virtual validProposalType(proposalType) {
function delegate(
Delegate[] calldata delegatees,
uint8 proposalType
) public virtual validProposalType(proposalType) activeDelegates(delegatees) {
address account = _msgSender();
_delegate(account, proposalType, delegatees);
}

/**
* @dev See {IWonderVotes-delegate}.
*/
function delegate(address delegatee, uint8 proposalType) public virtual validProposalType(proposalType) {
function delegate(
address delegatee,
uint8 proposalType
) public virtual validProposalType(proposalType) activeDelegate(delegatee) {
address account = _msgSender();
Delegate[] memory _singleDelegate = new Delegate[](1);
_singleDelegate[0] = Delegate({account: delegatee, weight: _weightNormalizer()});
Expand All @@ -150,7 +158,7 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes
/**
* @dev See {IWonderVotes-delegate}.
*/
function delegate(address delegatee) public virtual {
function delegate(address delegatee) public virtual activeDelegate(delegatee) {
address account = _msgSender();
Delegate[] memory _singleDelegate = new Delegate[](1);
_singleDelegate[0] = Delegate({account: delegatee, weight: _weightNormalizer()});
Expand Down Expand Up @@ -187,7 +195,7 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes
uint8 v,
bytes32 r,
bytes32 s
) public virtual validProposalType(proposalType) {
) public virtual validProposalType(proposalType) activeDelegates(delegatees) {
if (block.timestamp > expiry) {
revert VotesExpiredSignature(expiry);
}
Expand All @@ -209,7 +217,7 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes
uint8 v,
bytes32 r,
bytes32 s
) public virtual validProposalType(proposalType) {
) public virtual validProposalType(proposalType) activeDelegate(delegatee) {
Delegate[] memory _singleDelegate = new Delegate[](1);
_singleDelegate[0] = Delegate({account: delegatee, weight: _weightNormalizer()});
delegateBySig(_singleDelegate, proposalType, nonce, expiry, v, r, s);
Expand All @@ -225,7 +233,7 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
) public virtual activeDelegate(delegatee) {
Delegate[] memory _singleDelegate = new Delegate[](1);
_singleDelegate[0] = Delegate({account: delegatee, weight: _weightNormalizer()});

Expand All @@ -236,20 +244,35 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes
}
}

/**
* @dev See {IWonderVotes-isDelegable}.
*/
function isDelegable(address account) external view returns (bool) {
return !_nonDelegableAddresses[account];
}

/**
* @dev See {IWonderVotes-suspendDelegation}.
*/
function suspendDelegation(bool suspend) external {
_nonDelegableAddresses[msg.sender] = suspend;
emit DelegateSuspended(msg.sender, suspend);
}

/**
* @dev Delegate all of `account`'s voting units to `delegatee`.
*
* Emits events {IVotes-DelegateChanged} and {IVotes-DelegateVotesChanged}.
*/
function _delegate(address account, uint8 proposalType, Delegate[] memory delegatees) internal virtual {
if (delegatees.length > _maxDelegates()) revert DelegatesMaxNumberExceeded(delegatees.length);
if (delegatees.length > _maxDelegates()) revert VotesDelegatesMaxNumberExceeded(delegatees.length);

uint256 _weightSum;
for (uint256 i = 0; i < delegatees.length; i++) {
if (delegatees[i].weight == 0) revert ZeroWeight();
if (delegatees[i].weight == 0) revert VotesZeroWeight();
_weightSum += delegatees[i].weight;
}
if (_weightSum != _weightNormalizer()) revert InvalidWeightSum(_weightSum);
if (_weightSum != _weightNormalizer()) revert VotesInvalidWeightSum(_weightSum);

Delegate[] memory _oldDelegates = delegates(account, proposalType);

Expand Down Expand Up @@ -378,7 +401,25 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes
* @dev checks the `proposalType` validity
*/
modifier validProposalType(uint8 proposalType) {
if (!_validProposalType(proposalType)) revert InvalidProposalType(proposalType);
if (!_validProposalType(proposalType)) revert VotesInvalidProposalType(proposalType);
_;
}

/**
* @dev checks if the delegation is active for the `delegatee`
*/
modifier activeDelegate(address delegatee) {
if (_nonDelegableAddresses[delegatee]) revert VotesDelegationSuspended(delegatee);
_;
}

/**
* @dev checks if the delegation is active for the `delegatees`
*/
modifier activeDelegates(Delegate[] memory delegatees) {
for (uint256 i = 0; i < delegatees.length; i++) {
if (_nonDelegableAddresses[delegatees[i].account]) revert VotesDelegationSuspended(delegatees[i].account);
}
_;
}
}
34 changes: 30 additions & 4 deletions solidity/interfaces/governance/utils/IWonderVotes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,27 @@ interface IWonderVotes {
/**
* @dev The weight delegation sum is different from weightNormalizer.
*/
error InvalidWeightSum(uint256 weightSum);
error VotesInvalidWeightSum(uint256 weightSum);

/**
* @dev The weight set for a delegate is zero.
*/
error ZeroWeight();
error VotesZeroWeight();

/**
* @dev The proposal type is invalid.
*/
error InvalidProposalType(uint8 proposalType);
error VotesInvalidProposalType(uint8 proposalType);

/**
* @dev The delegates number for a `proposalType` exceeds the maximum number of delegates.
*/
error DelegatesMaxNumberExceeded(uint256 delegateesNumber);
error VotesDelegatesMaxNumberExceeded(uint256 delegateesNumber);

/**
* @dev The delegation of votes is suspended for the account.
*/
error VotesDelegationSuspended(address account);

/**
* @dev Emitted when an account changes their delegates.
Expand All @@ -48,6 +53,12 @@ interface IWonderVotes {
*/
event DelegateVotesChanged(address indexed delegate, uint8 proposalType, uint256 previousVotes, uint256 newVotes);

/**
* @dev Emitted when the delegation of new votes is suspended or resumed for a delegate.
* Note: changing the delegation status does not affect the already delegated votes to the account.
*/
event DelegateSuspended(address indexed delegate, bool suspend);

/**
* @dev Returns the current amount of votes that `account` has for a `proposalType`.
*/
Expand Down Expand Up @@ -120,6 +131,15 @@ interface IWonderVotes {
bytes32 s
) external;

/**
* @dev The caller account can enable or disable the ability to be delegated votes by a delegator.
* If set to true, the caller account is not eligible to be a delegatee; if set to false, it can be a delegatee.
*
* NOTE: changing the delegation status does not affect the already delegated votes to the account.
* By default, all accounts are allowed to be delegated.
*/
function suspendDelegation(bool suspend) external;

/**
* @dev Returns the amount that represents 100% of the weight sum for every delegation
* used to calculate the amount of votes when partial delegating to more than 1 delegate.
Expand All @@ -136,4 +156,10 @@ interface IWonderVotes {
* @dev Returns the `proposalTypes` supported.
*/
function proposalTypes() external view returns (uint8[] memory);

/**
* @dev Returns if the account is allowed to be delegated.
* Note: changing the delegation status does not affect the already delegated votes to the account.
*/
function isDelegable(address account) external view returns (bool);
}
133 changes: 133 additions & 0 deletions solidity/test/integration/Delegation.t.sol
Original file line number Diff line number Diff line change
@@ -1,35 +1,125 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import './IntegrationBase.t.sol';

Check warning on line 4 in solidity/test/integration/Delegation.t.sol

View workflow job for this annotation

GitHub Actions / Run Linters (18.x)

global import of path ./IntegrationBase.t.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)

Check warning on line 4 in solidity/test/integration/Delegation.t.sol

View workflow job for this annotation

GitHub Actions / Run Linters (18.x)

Import './IntegrationBase.t.sol' in contract Integration_Delegation should be declared as import {contract_to_import} from './IntegrationBase.t.sol';

import {WonderVotes} from 'contracts/governance/utils/WonderVotes.sol';

Check warning on line 6 in solidity/test/integration/Delegation.t.sol

View workflow job for this annotation

GitHub Actions / Run Linters (18.x)

imported name WonderVotes is not used
import {WonderGovernor} from 'contracts/governance/WonderGovernor.sol';

Check warning on line 7 in solidity/test/integration/Delegation.t.sol

View workflow job for this annotation

GitHub Actions / Run Linters (18.x)

imported name WonderGovernor is not used

contract Integration_Delegation is IntegrationBase {

Check warning on line 9 in solidity/test/integration/Delegation.t.sol

View workflow job for this annotation

GitHub Actions / Run Linters (18.x)

Contract name must be in CamelCase
function test_AllVotersDelegateToProposer() public {
// AllVoters delegates to proposer
for (uint256 _i = 0; _i < VOTERS_NUMBER; _i++) {
address holder = holders[_i];

Check warning on line 13 in solidity/test/integration/Delegation.t.sol

View workflow job for this annotation

GitHub Actions / Run Linters (18.x)

'holder' should start with _
vm.prank(holder);
rabbitToken.delegate(proposer);
}

for (uint256 _i = 0; _i < VOTERS_NUMBER; _i++) {
address holder = holders[_i];

Check warning on line 19 in solidity/test/integration/Delegation.t.sol

View workflow job for this annotation

GitHub Actions / Run Linters (18.x)

'holder' should start with _

for (uint256 _j = 0; _j < governor.proposalTypes().length; _j++) {
uint8 proposalType = governor.proposalTypes()[_j];

Check warning on line 22 in solidity/test/integration/Delegation.t.sol

View workflow job for this annotation

GitHub Actions / Run Linters (18.x)

'proposalType' should start with _
assertEq(rabbitToken.getVotes(holder, proposalType), 0);
}
}

for (uint256 _j = 0; _j < governor.proposalTypes().length; _j++) {
uint8 proposalType = governor.proposalTypes()[_j];

Check warning on line 28 in solidity/test/integration/Delegation.t.sol

View workflow job for this annotation

GitHub Actions / Run Linters (18.x)

'proposalType' should start with _
assertEq(rabbitToken.getVotes(proposer, proposalType), INITIAL_VOTERS_BALANCE * VOTERS_NUMBER);
}
}

function test_AllVotersDelegateAndTransferToProposerWithoutProposerDelegation() public {
for (uint256 _i = 0; _i < VOTERS_NUMBER; _i++) {
address holder = holders[_i];

Check warning on line 35 in solidity/test/integration/Delegation.t.sol

View workflow job for this annotation

GitHub Actions / Run Linters (18.x)

'holder' should start with _
vm.prank(holder);
rabbitToken.delegate(proposer);
}

for (uint256 _i = 0; _i < VOTERS_NUMBER; _i++) {
address holder = holders[_i];

for (uint256 _j = 0; _j < governor.proposalTypes().length; _j++) {
uint8 proposalType = governor.proposalTypes()[_j];
assertEq(rabbitToken.getVotes(holder, proposalType), 0);
}
}

for (uint256 _j = 0; _j < governor.proposalTypes().length; _j++) {
uint8 proposalType = governor.proposalTypes()[_j];
assertEq(rabbitToken.getVotes(proposer, proposalType), INITIAL_VOTERS_BALANCE * VOTERS_NUMBER);
}

for (uint256 _i = 0; _i < VOTERS_NUMBER; _i++) {
address holder = holders[_i];
vm.prank(holder);
IERC20(address(rabbitToken)).transfer(proposer, INITIAL_VOTERS_BALANCE);
}

for (uint256 _i = 0; _i < VOTERS_NUMBER; _i++) {
address holder = holders[_i];

for (uint256 _j = 0; _j < governor.proposalTypes().length; _j++) {
uint8 proposalType = governor.proposalTypes()[_j];
assertEq(rabbitToken.getVotes(holder, proposalType), 0);
}
}

// Since proposer never delegated himself, and the accounts that delegates proposer has 0 tokens, proposer has now NO votes
for (uint256 _j = 0; _j < governor.proposalTypes().length; _j++) {
uint8 proposalType = governor.proposalTypes()[_j];
assertEq(rabbitToken.getVotes(proposer, proposalType), 0);
}
}

function test_AllVotersDelegateAndTransferToProposerWithProposerDelegation() public {
for (uint256 _i = 0; _i < VOTERS_NUMBER; _i++) {
address holder = holders[_i];
vm.prank(holder);
rabbitToken.delegate(proposer);
}

for (uint256 _i = 0; _i < VOTERS_NUMBER; _i++) {
address holder = holders[_i];

for (uint256 _j = 0; _j < governor.proposalTypes().length; _j++) {
uint8 proposalType = governor.proposalTypes()[_j];
assertEq(rabbitToken.getVotes(holder, proposalType), 0);
}
}

for (uint256 _j = 0; _j < governor.proposalTypes().length; _j++) {
uint8 proposalType = governor.proposalTypes()[_j];
assertEq(rabbitToken.getVotes(proposer, proposalType), INITIAL_VOTERS_BALANCE * VOTERS_NUMBER);
}

// Proposer delegates to himself
vm.prank(proposer);
rabbitToken.delegate(proposer);

for (uint256 _i = 0; _i < VOTERS_NUMBER; _i++) {
address holder = holders[_i];
vm.prank(holder);
IERC20(address(rabbitToken)).transfer(proposer, INITIAL_VOTERS_BALANCE);
}

for (uint256 _i = 0; _i < VOTERS_NUMBER; _i++) {
address holder = holders[_i];

for (uint256 _j = 0; _j < governor.proposalTypes().length; _j++) {
uint8 proposalType = governor.proposalTypes()[_j];
assertEq(rabbitToken.getVotes(holder, proposalType), 0);
}
}

// Since proposer delegated himself, all the token transfers are now voting power of proposer
for (uint256 _j = 0; _j < governor.proposalTypes().length; _j++) {
uint8 proposalType = governor.proposalTypes()[_j];
assertEq(rabbitToken.getVotes(proposer, proposalType), INITIAL_VOTERS_BALANCE * VOTERS_NUMBER);
}
}

function test_AllVotersDelegateByProposalType() public {
uint8[] memory proposalTypes = governor.proposalTypes();

Expand Down Expand Up @@ -149,4 +239,47 @@
assertEq(rabbitToken.getVotes(proposer2, _proposalTypes[i]), INITIAL_VOTERS_BALANCE * VOTERS_NUMBER);
}
}

function test_DelegationSuspensionDoesNotAffectPreviousVotesDelegation() public {
// Delegates himself befor suspending
vm.prank(proposer);
rabbitToken.delegate(proposer);

uint8[] memory _proposalTypes = governor.proposalTypes();

// Holder 0 delegates to proposer before suspending
vm.prank(holders[0]);
rabbitToken.delegate(proposer);

for (uint8 i = 0; i < _proposalTypes.length; i++) {
assertEq(rabbitToken.getVotes(proposer, _proposalTypes[i]), INITIAL_VOTERS_BALANCE);
}

// Suspend delegation
vm.prank(proposer);
rabbitToken.suspendDelegation(true);

// Checks that delegation suspension does not affect previous delegation
for (uint8 i = 0; i < _proposalTypes.length; i++) {
assertEq(rabbitToken.getVotes(proposer, _proposalTypes[i]), INITIAL_VOTERS_BALANCE);
}

// Previous delegation also includes transfers that the holder 0 receives
vm.prank(holders[1]);
IERC20(address(rabbitToken)).transfer(holders[0], INITIAL_VOTERS_BALANCE);

// Checks that the votes is increased
for (uint8 i = 0; i < _proposalTypes.length; i++) {
assertEq(rabbitToken.getVotes(proposer, _proposalTypes[i]), INITIAL_VOTERS_BALANCE * 2);
}

// Holder 2 delegates to proposer which previously delegated himself before suspending
vm.prank(holders[2]);
IERC20(address(rabbitToken)).transfer(proposer, INITIAL_VOTERS_BALANCE);

// Checks that the votes of proposer is increased
for (uint8 i = 0; i < _proposalTypes.length; i++) {
assertEq(rabbitToken.getVotes(proposer, _proposalTypes[i]), INITIAL_VOTERS_BALANCE * 3);
}
}
}
Loading