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

Whitelisted proposers #108

Merged
merged 31 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ae0019d
Remove unnecessary code in a test
adamgall Oct 2, 2024
73db7fa
Update MockHats to actually track wearers of Hats
adamgall Oct 2, 2024
84e4312
Update IHats interface and Mock to implement isWearerOfHat convenienc…
adamgall Oct 2, 2024
d6ceda2
Create new contract LinearERC20VotingExtensible
adamgall Oct 2, 2024
88e89d7
Create a new Strategy which is based off of LinearERC20Voting, but mo…
adamgall Oct 2, 2024
1e96d39
Require at least one whitelisted hat upon creation
adamgall Oct 7, 2024
b79b43f
Add comments
adamgall Oct 7, 2024
8d9b4d8
Add new strategy for ERC721 voting but Hats proposal creation
adamgall Oct 7, 2024
73f5ad8
Extract HatsProposalCreationWhitelist code into own contract, and inh…
adamgall Oct 7, 2024
ee48e3d
Move HatsProposalCreationWhitelist tests to own file using new specif…
adamgall Oct 8, 2024
7a1605a
Create test for new LinearERC721VotingWithHatsProposals
adamgall Oct 8, 2024
51c539e
Undo changes to IHats interface, so as to not create changes in Decen…
adamgall Oct 9, 2024
e1fd7d7
Copy in the FULL Hats interface(s) and use those in HatsProposalCreat…
adamgall Oct 9, 2024
ccf08fa
Create deployment scripts for two new strategies
adamgall Oct 9, 2024
095bafc
Move new Hats interfaces into new "full" directory
adamgall Oct 9, 2024
821b4ba
Merge branch 'develop' into whitelisted-proposers
adamgall Oct 9, 2024
c10803f
Deployment to Sepolia
mudrila Oct 23, 2024
fc25431
Merge branch 'develop' into whitelisted-proposers
adamgall Nov 1, 2024
2837bb2
Fix deployment script names
adamgall Nov 1, 2024
22b9dba
Lint and pretty
adamgall Nov 1, 2024
4232195
Move new strategy contracts into a new strategy directory
adamgall Nov 5, 2024
298db7e
Remove "full" hats interface subdir which was accidentally added back
adamgall Nov 5, 2024
72a3400
Remove deployment artifacts from this branch
adamgall Nov 5, 2024
21d2c4b
Fix typo
adamgall Nov 5, 2024
57a5e6e
Merge branch 'develop' into whitelisted-proposers
adamgall Nov 5, 2024
8b4b568
Merge branch 'develop' into whitelisted-proposers
adamgall Nov 6, 2024
93c0a97
Update new deployment script file numbers
adamgall Nov 6, 2024
436d899
Implement some simplification in HatsProposalCreationWhitelist
adamgall Nov 6, 2024
e6e0bd1
Add tests to confirm only owner can whitelist hats
adamgall Nov 6, 2024
f0d0acd
Add more tests to confirm non-owner cannot remove hat from whitelist
adamgall Nov 6, 2024
3992445
Already had these tests!
adamgall Nov 6, 2024
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
327 changes: 327 additions & 0 deletions contracts/azorius/LinearERC20VotingExtensible.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,327 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity =0.8.19;

import {IVotes} from "@openzeppelin/contracts/governance/utils/IVotes.sol";
import {BaseStrategy, IBaseStrategy} from "./BaseStrategy.sol";
import {BaseQuorumPercent} from "./BaseQuorumPercent.sol";
import {BaseVotingBasisPercent} from "./BaseVotingBasisPercent.sol";

/**
* An [Azorius](./Azorius.md) [BaseStrategy](./BaseStrategy.md) implementation that
adamgall marked this conversation as resolved.
Show resolved Hide resolved
* enables linear (i.e. 1 to 1) token voting. Each token delegated to a given address
* in an `ERC20Votes` token equals 1 vote for a Proposal.
*/
abstract contract LinearERC20VotingExtensible is
BaseStrategy,
BaseQuorumPercent,
BaseVotingBasisPercent
{
/**
* The voting options for a Proposal.
*/
enum VoteType {
NO, // disapproves of executing the Proposal
YES, // approves of executing the Proposal
ABSTAIN // neither YES nor NO, i.e. voting "present"
}

/**
* Defines the current state of votes on a particular Proposal.
*/
struct ProposalVotes {
uint32 votingStartBlock; // block that voting starts at
uint32 votingEndBlock; // block that voting ends
uint256 noVotes; // current number of NO votes for the Proposal
uint256 yesVotes; // current number of YES votes for the Proposal
uint256 abstainVotes; // current number of ABSTAIN votes for the Proposal
mapping(address => bool) hasVoted; // whether a given address has voted yet or not
}

IVotes public governanceToken;

/** Number of blocks a new Proposal can be voted on. */
uint32 public votingPeriod;

/** Voting weight required to be able to submit Proposals. */
uint256 public requiredProposerWeight;

/** `proposalId` to `ProposalVotes`, the voting state of a Proposal. */
mapping(uint256 => ProposalVotes) internal proposalVotes;

event VotingPeriodUpdated(uint32 votingPeriod);
event RequiredProposerWeightUpdated(uint256 requiredProposerWeight);
event ProposalInitialized(uint32 proposalId, uint32 votingEndBlock);
event Voted(
address voter,
uint32 proposalId,
uint8 voteType,
uint256 weight
);

error InvalidProposal();
error VotingEnded();
error AlreadyVoted();
error InvalidVote();
error InvalidTokenAddress();

/**
* Sets up the contract with its initial parameters.
*
* @param initializeParams encoded initialization parameters: `address _owner`,
* `IVotes _governanceToken`, `address _azoriusModule`, `uint32 _votingPeriod`,
* `uint256 _requiredProposerWeight`, `uint256 _quorumNumerator`,
* `uint256 _basisNumerator`
*/
function setUp(
bytes memory initializeParams
) public virtual override initializer {
(
address _owner,
IVotes _governanceToken,
address _azoriusModule,
uint32 _votingPeriod,
uint256 _requiredProposerWeight,
uint256 _quorumNumerator,
uint256 _basisNumerator
) = abi.decode(
initializeParams,
(address, IVotes, address, uint32, uint256, uint256, uint256)
);
if (address(_governanceToken) == address(0))
revert InvalidTokenAddress();

governanceToken = _governanceToken;
__Ownable_init();
transferOwnership(_owner);
_setAzorius(_azoriusModule);
_updateQuorumNumerator(_quorumNumerator);
_updateBasisNumerator(_basisNumerator);
_updateVotingPeriod(_votingPeriod);
_updateRequiredProposerWeight(_requiredProposerWeight);

emit StrategySetUp(_azoriusModule, _owner);
}

/**
* Updates the voting time period for new Proposals.
*
* @param _votingPeriod voting time period (in blocks)
*/
function updateVotingPeriod(
uint32 _votingPeriod
) external virtual onlyOwner {
_updateVotingPeriod(_votingPeriod);
}

/**
* Updates the voting weight required to submit new Proposals.
*
* @param _requiredProposerWeight required token voting weight
*/
function updateRequiredProposerWeight(
uint256 _requiredProposerWeight
) external virtual onlyOwner {
_updateRequiredProposerWeight(_requiredProposerWeight);
}

/**
* Casts votes for a Proposal, equal to the caller's token delegation.
*
* @param _proposalId id of the Proposal to vote on
* @param _voteType Proposal support as defined in VoteType (NO, YES, ABSTAIN)
*/
function vote(uint32 _proposalId, uint8 _voteType) external virtual {
_vote(
_proposalId,
msg.sender,
_voteType,
getVotingWeight(msg.sender, _proposalId)
);
}

/**
* Returns the current state of the specified Proposal.
*
* @param _proposalId id of the Proposal
* @return noVotes current count of "NO" votes
* @return yesVotes current count of "YES" votes
* @return abstainVotes current count of "ABSTAIN" votes
* @return startBlock block number voting starts
* @return endBlock block number voting ends
*/
function getProposalVotes(
uint32 _proposalId
)
external
view
virtual
returns (
uint256 noVotes,
uint256 yesVotes,
uint256 abstainVotes,
uint32 startBlock,
uint32 endBlock,
uint256 votingSupply
)
{
noVotes = proposalVotes[_proposalId].noVotes;
yesVotes = proposalVotes[_proposalId].yesVotes;
abstainVotes = proposalVotes[_proposalId].abstainVotes;
startBlock = proposalVotes[_proposalId].votingStartBlock;
endBlock = proposalVotes[_proposalId].votingEndBlock;
votingSupply = getProposalVotingSupply(_proposalId);
}

/** @inheritdoc BaseStrategy*/
function initializeProposal(
bytes memory _data
) public virtual override onlyAzorius {
uint32 proposalId = abi.decode(_data, (uint32));
uint32 _votingEndBlock = uint32(block.number) + votingPeriod;

proposalVotes[proposalId].votingEndBlock = _votingEndBlock;
proposalVotes[proposalId].votingStartBlock = uint32(block.number);

emit ProposalInitialized(proposalId, _votingEndBlock);
}

/**
* Returns whether an address has voted on the specified Proposal.
*
* @param _proposalId id of the Proposal to check
* @param _address address to check
* @return bool true if the address has voted on the Proposal, otherwise false
*/
function hasVoted(
uint32 _proposalId,
address _address
) public view virtual returns (bool) {
return proposalVotes[_proposalId].hasVoted[_address];
}

/** @inheritdoc BaseStrategy*/
function isPassed(
uint32 _proposalId
) public view virtual override returns (bool) {
return (block.number > proposalVotes[_proposalId].votingEndBlock && // voting period has ended
meetsQuorum(
getProposalVotingSupply(_proposalId),
proposalVotes[_proposalId].yesVotes,
proposalVotes[_proposalId].abstainVotes
) && // yes + abstain votes meets the quorum
meetsBasis(
proposalVotes[_proposalId].yesVotes,
proposalVotes[_proposalId].noVotes
)); // yes votes meets the basis
}

/**
* Returns a snapshot of total voting supply for a given Proposal. Because token supplies can change,
* it is necessary to calculate quorum from the supply available at the time of the Proposal's creation,
* not when it is being voted on passes / fails.
*
* @param _proposalId id of the Proposal
* @return uint256 voting supply snapshot for the given _proposalId
*/
function getProposalVotingSupply(
uint32 _proposalId
) public view virtual returns (uint256) {
return
governanceToken.getPastTotalSupply(
proposalVotes[_proposalId].votingStartBlock
);
}

/**
* Calculates the voting weight an address has for a specific Proposal.
*
* @param _voter address of the voter
* @param _proposalId id of the Proposal
* @return uint256 the address' voting weight
*/
function getVotingWeight(
address _voter,
uint32 _proposalId
) public view virtual returns (uint256) {
return
governanceToken.getPastVotes(
_voter,
proposalVotes[_proposalId].votingStartBlock
);
}

/** @inheritdoc BaseStrategy*/
function isProposer(
address _address
) public view virtual override returns (bool) {
return
governanceToken.getPastVotes(_address, block.number - 1) >=
requiredProposerWeight;
}

/** @inheritdoc BaseStrategy*/
function votingEndBlock(
uint32 _proposalId
) public view virtual override returns (uint32) {
return proposalVotes[_proposalId].votingEndBlock;
}

/** Internal implementation of `updateVotingPeriod`. */
function _updateVotingPeriod(uint32 _votingPeriod) internal virtual {
votingPeriod = _votingPeriod;
emit VotingPeriodUpdated(_votingPeriod);
}

/** Internal implementation of `updateRequiredProposerWeight`. */
function _updateRequiredProposerWeight(
uint256 _requiredProposerWeight
) internal virtual {
requiredProposerWeight = _requiredProposerWeight;
emit RequiredProposerWeightUpdated(_requiredProposerWeight);
}

/**
* Internal function for casting a vote on a Proposal.
*
* @param _proposalId id of the Proposal
* @param _voter address casting the vote
* @param _voteType vote support, as defined in VoteType
* @param _weight amount of voting weight cast, typically the
* total number of tokens delegated
*/
function _vote(
uint32 _proposalId,
address _voter,
uint8 _voteType,
uint256 _weight
) internal virtual {
if (proposalVotes[_proposalId].votingEndBlock == 0)
revert InvalidProposal();
if (block.number > proposalVotes[_proposalId].votingEndBlock)
revert VotingEnded();
if (proposalVotes[_proposalId].hasVoted[_voter]) revert AlreadyVoted();

proposalVotes[_proposalId].hasVoted[_voter] = true;

if (_voteType == uint8(VoteType.NO)) {
proposalVotes[_proposalId].noVotes += _weight;
} else if (_voteType == uint8(VoteType.YES)) {
proposalVotes[_proposalId].yesVotes += _weight;
} else if (_voteType == uint8(VoteType.ABSTAIN)) {
proposalVotes[_proposalId].abstainVotes += _weight;
} else {
revert InvalidVote();
}

emit Voted(_voter, _proposalId, _voteType, _weight);
}

/** @inheritdoc BaseQuorumPercent*/
function quorumVotes(
uint32 _proposalId
) public view virtual override returns (uint256) {
return
(quorumNumerator * getProposalVotingSupply(_proposalId)) /
QUORUM_DENOMINATOR;
}
}
Loading