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

Add cancel logic and tests #36

Merged
merged 7 commits into from
Oct 23, 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
33 changes: 32 additions & 1 deletion contracts/CompoundGovernor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,37 @@ contract CompoundGovernor is
_setProposalGuardian(_proposalGuardian);
}

/// @notice Cancels an active proposal.
/// @notice This function can be called by the proposer, the proposal guardian, or anyone if the proposer's voting
/// power has dropped below the proposal threshold. For whitelisted proposers, only special actors (proposer,
/// proposal guardian, whitelist guardian) can cancel if the proposer is below the threshold.
/// @param targets An array of addresses that will be called if the proposal is executed.
/// @param values An array of ETH values to be sent to each address when the proposal is executed.
/// @param calldatas An array of calldata to be sent to each address when the proposal is executed.
/// @param descriptionHash The hash of the proposal's description string.
/// @return uint256 The ID of the canceled proposal.
function cancel(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: this will also have to be reproduced for the cancel(uint proposalId) method in #3.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

created an issue so we don't forget #37

address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) public override returns (uint256) {
uint256 _proposalId = hashProposal(targets, values, calldatas, descriptionHash);
address _proposer = proposalProposer(_proposalId);

if (msg.sender != _proposer && msg.sender != proposalGuardian.account) {
if (token().getPriorVotes(_proposer, block.number - 1) >= proposalThreshold()) {
wildmolasses marked this conversation as resolved.
Show resolved Hide resolved
revert Unauthorized("Proposer above proposalThreshold", msg.sender);
}

if (isWhitelisted(_proposer) && msg.sender != whitelistGuardian) {
revert Unauthorized("Not whitelistGuardian", msg.sender);
}
}

return _cancel(targets, values, calldatas, descriptionHash);
}

/// @notice Sets or updates the whitelist expiration for a specific account.
/// @notice A whitelisted account's proposals cannot be canceled by anyone except the `whitelistGuardian` when its
/// voting weight falls below the `proposalThreshold`.
Expand All @@ -139,7 +170,7 @@ contract CompoundGovernor is
/// weight.
/// @param _account The address of the account to check.
/// @return bool Returns true if the account is whitelisted (expiration is in the future), false otherwise.
function isWhitelisted(address _account) external view returns (bool) {
function isWhitelisted(address _account) public view returns (bool) {
return (whitelistAccountExpirations[_account] > block.timestamp);
}

Expand Down
165 changes: 165 additions & 0 deletions contracts/test/CompoundGovernorCancelTest.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// SPDX-License-Identifier: BSD-3-Clause

pragma solidity 0.8.26;

import {ProposalTest} from "contracts/test/helpers/ProposalTest.sol";
import {IGovernor} from "@openzeppelin/contracts/governance/IGovernor.sol";
import {CompoundGovernor} from "contracts/CompoundGovernor.sol";

contract CompoundGovernorCancelTest is ProposalTest {
function _buildAnEmptyProposal() private pure returns (Proposal memory _proposal) {
address[] memory _targets = new address[](1);
uint256[] memory _values = new uint256[](1);
bytes[] memory _calldatas = new bytes[](1);
_proposal = Proposal(_targets, _values, _calldatas, "An Empty Proposal");
}

function _getProposalId(Proposal memory _proposal) private view returns (uint256) {
return governor.hashProposal(
_proposal.targets, _proposal.values, _proposal.calldatas, keccak256(bytes(_proposal.description))
);
}

function _setWhitelistedProposer(address _proposer) private {
vm.prank(whitelistGuardian);
governor.setWhitelistAccountExpiration(_proposer, block.timestamp + 2_000_000);
}

function _removeDelegateeVotingWeight(address _proposer) private {
vm.mockCall(
address(token),
abi.encodeWithSelector(token.getPriorVotes.selector, _proposer, block.number - 1),
abi.encode(0) // Return 0 as the new voting weight
);
}

function testFuzz_ProposerCanCancelItsOwnProposal(uint256 _randomIndex) public {
_randomIndex = bound(_randomIndex, 0, _majorDelegates.length - 1);
address _proposer = _majorDelegates[_randomIndex];
Proposal memory _proposal = _buildAnEmptyProposal();
uint256 _proposalId = _getProposalId(_proposal);
_submitPassAndQueueProposal(_proposer, _proposal);

vm.prank(_proposer);
governor.cancel(
_proposal.targets, _proposal.values, _proposal.calldatas, keccak256(bytes(_proposal.description))
);
vm.assertEq(uint256(governor.state(_proposalId)), uint256(IGovernor.ProposalState.Canceled));
}

function testFuzz_ProposalGuardianCanCancelAnyProposal(uint256 _randomIndex) public {
_randomIndex = bound(_randomIndex, 0, _majorDelegates.length - 1);
address _proposer = _majorDelegates[_randomIndex];
Proposal memory _proposal = _buildAnEmptyProposal();
uint256 _proposalId = _getProposalId(_proposal);
_submitPassAndQueueProposal(_proposer, _proposal);

vm.prank(proposalGuardian.account);
governor.cancel(
_proposal.targets, _proposal.values, _proposal.calldatas, keccak256(bytes(_proposal.description))
);
vm.assertEq(uint256(governor.state(_proposalId)), uint256(IGovernor.ProposalState.Canceled));
}

function testFuzz_AnyoneCanCancelAProposalBelowThreshold(address _caller, uint256 _randomIndex) public {
vm.assume(_caller != PROXY_ADMIN_ADDRESS);
_randomIndex = bound(_randomIndex, 0, _majorDelegates.length - 1);
address _proposer = _majorDelegates[_randomIndex];
Proposal memory _proposal = _buildAnEmptyProposal();
uint256 _proposalId = _getProposalId(_proposal);
_submitPassAndQueueProposal(_proposer, _proposal);
_removeDelegateeVotingWeight(_proposer);

vm.prank(_caller);
governor.cancel(
_proposal.targets, _proposal.values, _proposal.calldatas, keccak256(bytes(_proposal.description))
);
vm.assertEq(uint256(governor.state(_proposalId)), uint256(IGovernor.ProposalState.Canceled));
}

function testFuzz_WhitelistGuardianCanCancelWhitelistedProposalBelowThreshold(uint256 _randomIndex) public {
_randomIndex = bound(_randomIndex, 0, _majorDelegates.length - 1);
address _proposer = _majorDelegates[_randomIndex];
Proposal memory _proposal = _buildAnEmptyProposal();
uint256 _proposalId = _getProposalId(_proposal);
_setWhitelistedProposer(_proposer);
_submitPassAndQueueProposal(_proposer, _proposal);
_removeDelegateeVotingWeight(_proposer);

vm.prank(whitelistGuardian);
governor.cancel(
_proposal.targets, _proposal.values, _proposal.calldatas, keccak256(bytes(_proposal.description))
);
vm.assertEq(uint256(governor.state(_proposalId)), uint256(IGovernor.ProposalState.Canceled));
}

function testFuzz_RevertIf_NonProposerOrGuardianCancelsProposalAboveThreshold(address _caller, uint256 _randomIndex)
public
{
_randomIndex = bound(_randomIndex, 0, _majorDelegates.length - 1);
address _proposer = _majorDelegates[_randomIndex];
vm.assume(_caller != proposalGuardian.account && _caller != _proposer && _caller != PROXY_ADMIN_ADDRESS);
Proposal memory _proposal = _buildAnEmptyProposal();
_submitPassAndQueueProposal(_proposer, _proposal);

vm.prank(_caller);
vm.expectRevert(
abi.encodeWithSelector(
CompoundGovernor.Unauthorized.selector, bytes32("Proposer above proposalThreshold"), _caller
)
);
governor.cancel(
_proposal.targets, _proposal.values, _proposal.calldatas, keccak256(bytes(_proposal.description))
);
}

function testFuzz_RevertIf_NonWhitelistGuardianCancelsWhitelistedProposalBelowThreshold(
address _caller,
uint256 _randomIndex
) public {
_randomIndex = bound(_randomIndex, 0, _majorDelegates.length - 1);
address _proposer = _majorDelegates[_randomIndex];
vm.assume(
_caller != whitelistGuardian && _caller != _proposer && _caller != proposalGuardian.account
&& _caller != PROXY_ADMIN_ADDRESS
);

Proposal memory _proposal = _buildAnEmptyProposal();
_setWhitelistedProposer(_proposer);
_submitPassAndQueueProposal(_proposer, _proposal);
_removeDelegateeVotingWeight(_proposer);

vm.prank(_caller);
vm.expectRevert(
abi.encodeWithSelector(CompoundGovernor.Unauthorized.selector, bytes32("Not whitelistGuardian"), _caller)
);
governor.cancel(
_proposal.targets, _proposal.values, _proposal.calldatas, keccak256(bytes(_proposal.description))
);
}

function testFuzz_RevertIf_NonProposerOrGuardianCancelsWhitelistedProposalAboveThreshold(
address _caller,
uint256 _randomIndex
) public {
_randomIndex = bound(_randomIndex, 0, _majorDelegates.length - 1);
address _proposer = _majorDelegates[_randomIndex];
vm.assume(_caller != _proposer && _caller != proposalGuardian.account && _caller != PROXY_ADMIN_ADDRESS);
Proposal memory _proposal = _buildAnEmptyProposal();
vm.prank(whitelistGuardian);
governor.setWhitelistAccountExpiration(_proposer, block.timestamp + 2_000_000);
vm.assertTrue(governor.isWhitelisted(_proposer));

_submitPassAndQueueProposal(_proposer, _proposal);

vm.prank(_caller);
vm.expectRevert(
abi.encodeWithSelector(
CompoundGovernor.Unauthorized.selector, bytes32("Proposer above proposalThreshold"), _caller
)
);
governor.cancel(
_proposal.targets, _proposal.values, _proposal.calldatas, keccak256(bytes(_proposal.description))
);
}
}
9 changes: 4 additions & 5 deletions contracts/test/GovernorVotesCompUpgradeable.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,11 @@ contract GovernorVotesCompUpgradeableTest is Test, CompoundGovernorConstants {
}

function testFuzz_ReturnsCorrectVotes(uint256 _blockNumber) public view {
_blockNumber = bound(_blockNumber, 0, FORK_BLOCK - 1);
_blockNumber = bound(_blockNumber, 21_000_000, FORK_BLOCK - 1);
for (uint256 i; i < _majorDelegates.length; i++) {
assertEq(
governorVotes.getVotes(_majorDelegates[i], _blockNumber),
IComp(COMP_TOKEN_ADDRESS).getPriorVotes(_majorDelegates[i], _blockNumber)
);
uint256 _votingWeight = IComp(COMP_TOKEN_ADDRESS).getPriorVotes(_majorDelegates[i], _blockNumber);
assertTrue(_votingWeight > 0);
assertEq(governorVotes.getVotes(_majorDelegates[i], _blockNumber), _votingWeight);
}
}
}
40 changes: 20 additions & 20 deletions script/CompoundGovernorConstants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,33 +21,33 @@ contract CompoundGovernorConstants {
address payable TIMELOCK_ADDRESS = payable(0x6d903f6003cca6255D85CcA4D3B5E5146dC33925);

// The address of the proxy admin
address PROXY_ADMIN_ADDRESS = 0x05B96dd728edab708344F69C11854e98D4ec8FDA;
address PROXY_ADMIN_ADDRESS = 0x2fcE287db7d15a6eb0b83390BE228650689A64b6;
address COMMUNITY_MULTISIG_ADDRESS = 0xbbf3f1421D886E9b2c5D716B5192aC998af2012c; // Current proposal guardian.

// The fork block for testing
uint256 FORK_BLOCK = 20_885_000;
uint256 FORK_BLOCK = 21_017_323;

address[] public _majorDelegates;

constructor() {
_majorDelegates = new address[](18);
_majorDelegates[0] = 0x1B686eE8E31c5959D9F5BBd8122a58682788eeaD; // L2BEAT
_majorDelegates[1] = 0xF4B0556B9B6F53E00A1FDD2b0478Ce841991D8fA; // olimpio
_majorDelegates[2] = 0x11cd09a0c5B1dc674615783b0772a9bFD53e3A8F; // Gauntlet
_majorDelegates[3] = 0xB933AEe47C438f22DE0747D57fc239FE37878Dd1; // Wintermute
_majorDelegates[4] = 0x0eB5B03c0303f2F47cD81d7BE4275AF8Ed347576; // Treasure
_majorDelegates[5] = 0xF92F185AbD9E00F56cb11B0b709029633d1E37B4; //
_majorDelegates[6] = 0x186e505097BFA1f3cF45c2C9D7a79dE6632C3cdc;
_majorDelegates[7] = 0x5663D01D8109DDFC8aACf09fBE51F2d341bb3643;
_majorDelegates[8] = 0x2ef27b114917dD53f8633440A7C0328fef132e2F; // MUX Protocol
_majorDelegates[9] = 0xE48C655276C23F1534AE2a87A2bf8A8A6585Df70; // ercwl
_majorDelegates[10] = 0x8A3e9846df0CDc723C06e4f0C642ffFF82b54610;
_majorDelegates[11] = 0xAD16ebE6FfC7d96624A380F394cD64395B0C6144; // DK (Premia)
_majorDelegates[12] = 0xA5dF0cf3F95C6cd97d998b9D990a86864095d9b0; // Blockworks Research
_majorDelegates[13] = 0x839395e20bbB182fa440d08F850E6c7A8f6F0780; // Griff Green
_majorDelegates[14] = 0x2e3BEf6830Ae84bb4225D318F9f61B6b88C147bF; // Camelot
_majorDelegates[15] = 0x8F73bE66CA8c79382f72139be03746343Bf5Faa0; // mihal.eth
_majorDelegates[16] = 0xb5B069370Ef24BC67F114e185D185063CE3479f8; // Frisson
_majorDelegates[17] = 0xdb5781a835b60110298fF7205D8ef9678Ff1f800; // yoav.eth
_majorDelegates[0] = 0x9AA835Bc7b8cE13B9B0C9764A52FbF71AC62cCF1; // a16z
_majorDelegates[1] = 0x7E959eAB54932f5cFd10239160a7fd6474171318;
_majorDelegates[2] = 0x8169522c2C57883E8EF80C498aAB7820dA539806; // Geoffrey Hayes
_majorDelegates[3] = 0x683a4F9915D6216f73d6Df50151725036bD26C02; // Gauntlet
_majorDelegates[4] = 0x8d07D225a769b7Af3A923481E1FdF49180e6A265; // MonetSupply
_majorDelegates[5] = 0x66cD62c6F8A4BB0Cd8720488BCBd1A6221B765F9; // allthecolors
_majorDelegates[6] = 0x2210dc066aacB03C9676C4F1b36084Af14cCd02E; // bryancolligan
_majorDelegates[7] = 0x070341aA5Ed571f0FB2c4a5641409B1A46b4961b; // Franklin DAO
_majorDelegates[8] = 0xB933AEe47C438f22DE0747D57fc239FE37878Dd1; // Wintermute Governance
_majorDelegates[9] = 0x13BDaE8c5F0fC40231F0E6A4ad70196F59138548; // Michigan Blockchain
_majorDelegates[10] = 0x3FB19771947072629C8EEE7995a2eF23B72d4C8A; // PGov
_majorDelegates[11] = 0xB49f8b8613bE240213C1827e2E576044fFEC7948; // Avantgarde
_majorDelegates[12] = 0x54A37d93E57c5DA659F508069Cf65A381b61E189; // blck
_majorDelegates[13] = 0x7d1a02C0ebcF06E1A36231A54951E061673ab27f;
_majorDelegates[14] = 0xb35659cbac913D5E4119F2Af47fD490A45e2c826; // Event Horizon DAO
_majorDelegates[15] = 0x47C125DEe6898b6CB2379bCBaFC823Ff3f614770; // blockchainucla
_majorDelegates[16] = 0x7AE109A63ff4DC852e063a673b40BED85D22E585; // CalBlockchain
_majorDelegates[17] = 0xed11e5eA95a5A3440fbAadc4CC404C56D0a5bb04; // she256.eth
}
}
Loading