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

Mainnet polygon deployment cleanup #96

Merged
merged 2 commits into from
Jul 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
273 changes: 273 additions & 0 deletions contracts/azorius/LinearERC20VotingOG.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
// 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
* 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.
*/
contract LinearERC20VotingOG 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`,
* `ERC20Votes _governanceToken`, `address _azoriusModule`, `uint256 _votingPeriod`,
* `uint256 _quorumNumerator`, `uint256 _basisNumerator`
*/
function setUp(bytes memory initializeParams) public 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 onlyOwner {
_updateVotingPeriod(_votingPeriod);
}

/**
* Updates the voting weight required to submit new Proposals.
*
* @param _requiredProposerWeight required token voting weight
*/
function updateRequiredProposerWeight(uint256 _requiredProposerWeight) external 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 {
_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
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 returns (bool) {
return proposalVotes[_proposalId].hasVoted[_address];
}

/** @inheritdoc BaseStrategy*/
function isPassed(uint32 _proposalId) public view 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 returns (uint256) {
return
governanceToken.getPastVotes(
_voter,
proposalVotes[_proposalId].votingStartBlock
);
}

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

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

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

/** Internal implementation of `updateRequiredProposerWeight`. */
function _updateRequiredProposerWeight(uint256 _requiredProposerWeight) internal {
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 {
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 override returns (uint256) {
return quorumNumerator * getProposalVotingSupply(_proposalId) / QUORUM_DENOMINATOR;
}
}
7 changes: 7 additions & 0 deletions deploy/core/010_deploy_LinearERC20Voting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ import { DeployFunction } from "hardhat-deploy/types";
import { deployNonUpgradeable } from "../helpers/deployNonUpgradeable";

const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
const chainId = await hre.getChainId();

// See https://github.com/decentdao/decent-contracts/pull/96
if (chainId === "1" || chainId === "137") {
return;
}

await deployNonUpgradeable(hre, "LinearERC20Voting", []);
};

Expand Down
7 changes: 7 additions & 0 deletions deploy/core/014_deploy_LinearERC20WrappedVoting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ import { DeployFunction } from "hardhat-deploy/types";
import { deployNonUpgradeable } from "../helpers/deployNonUpgradeable";

const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
const chainId = await hre.getChainId();

// See https://github.com/decentdao/decent-contracts/pull/96
if (chainId === "1" || chainId === "137") {
return;
}

await deployNonUpgradeable(hre, "LinearERC20WrappedVoting", []);
};

Expand Down