From 3bb6de7893f527278ff4d78da69fd3d66fc1f1c2 Mon Sep 17 00:00:00 2001 From: arr00 <13561405+arr00@users.noreply.github.com> Date: Tue, 16 Nov 2021 13:26:45 -0800 Subject: [PATCH] Add Governance Whitelist Users Feature (#149) Governance whitelist allows anyone on the whitelist to make a proposal without meeting the COMP threshold requirements. --- .../Governance/GovernorBravoDelegate.sol | 50 ++- .../Governance/GovernorBravoDelegateG1.sol | 388 ++++++++++++++++++ .../Governance/GovernorBravoInterfaces.sol | 14 + networks/mainnet.json | 39 +- scenario/src/Contract/GovernorBravo.ts | 3 + scenario/src/Event/GovernorBravoEvent.ts | 76 ++++ scenario/src/Value/GovernorBravoValue.ts | 26 +- .../GovernorBravo/WhitelistedAccount.scen | 115 ++++++ .../hypothetical_upgrade.scen | 40 ++ .../hypothetical_upgrade_post_deploy.scen | 48 +++ .../hypothetical_upgrade_post_propose.scen | 47 +++ 11 files changed, 832 insertions(+), 14 deletions(-) create mode 100644 contracts/Governance/GovernorBravoDelegateG1.sol create mode 100644 spec/scenario/GovernorBravo/WhitelistedAccount.scen create mode 100755 spec/sim/0011-whitelist-accounts/hypothetical_upgrade.scen create mode 100755 spec/sim/0011-whitelist-accounts/hypothetical_upgrade_post_deploy.scen create mode 100755 spec/sim/0011-whitelist-accounts/hypothetical_upgrade_post_propose.scen diff --git a/contracts/Governance/GovernorBravoDelegate.sol b/contracts/Governance/GovernorBravoDelegate.sol index 3b06db622..cc8ea398a 100644 --- a/contracts/Governance/GovernorBravoDelegate.sol +++ b/contracts/Governance/GovernorBravoDelegate.sol @@ -3,7 +3,7 @@ pragma experimental ABIEncoderV2; import "./GovernorBravoInterfaces.sol"; -contract GovernorBravoDelegate is GovernorBravoDelegateStorageV1, GovernorBravoEvents { +contract GovernorBravoDelegate is GovernorBravoDelegateStorageV2, GovernorBravoEvents { /// @notice The name of this contract string public constant name = "Compound Governor Bravo"; @@ -74,7 +74,8 @@ contract GovernorBravoDelegate is GovernorBravoDelegateStorageV1, GovernorBravoE function propose(address[] memory targets, uint[] memory values, string[] memory signatures, bytes[] memory calldatas, string memory description) public returns (uint) { // Reject proposals before initiating as Governor require(initialProposalId != 0, "GovernorBravo::propose: Governor Bravo not active"); - require(comp.getPriorVotes(msg.sender, sub256(block.number, 1)) > proposalThreshold, "GovernorBravo::propose: proposer votes below proposal threshold"); + // Allow addresses above proposal threshold and whitelisted addresses to propose + require(comp.getPriorVotes(msg.sender, sub256(block.number, 1)) > proposalThreshold || isWhitelisted(msg.sender), "GovernorBravo::propose: proposer votes below proposal threshold"); require(targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length, "GovernorBravo::propose: proposal function information arity mismatch"); require(targets.length != 0, "GovernorBravo::propose: must provide actions"); require(targets.length <= proposalMaxOperations, "GovernorBravo::propose: too many actions"); @@ -156,8 +157,18 @@ contract GovernorBravoDelegate is GovernorBravoDelegateStorageV1, GovernorBravoE require(state(proposalId) != ProposalState.Executed, "GovernorBravo::cancel: cannot cancel executed proposal"); Proposal storage proposal = proposals[proposalId]; - require(msg.sender == proposal.proposer || comp.getPriorVotes(proposal.proposer, sub256(block.number, 1)) < proposalThreshold, "GovernorBravo::cancel: proposer above threshold"); + // Proposer can cancel + if(msg.sender != proposal.proposer) { + // Whitelisted proposers can't be canceled for falling below proposal threshold + if(isWhitelisted(proposal.proposer)) { + require((comp.getPriorVotes(proposal.proposer, sub256(block.number, 1)) < proposalThreshold) && msg.sender == whitelistGuardian, "GovernorBravo::cancel: whitelisted proposer"); + } + else { + require((comp.getPriorVotes(proposal.proposer, sub256(block.number, 1)) < proposalThreshold), "GovernorBravo::cancel: proposer above threshold"); + } + } + proposal.canceled = true; for (uint i = 0; i < proposal.targets.length; i++) { timelock.cancelTransaction(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], proposal.eta); @@ -275,6 +286,15 @@ contract GovernorBravoDelegate is GovernorBravoDelegateStorageV1, GovernorBravoE return votes; } + /** + * @notice View function which returns if an account is whitelisted + * @param account Account to check white list status of + * @return If the account is whitelisted + */ + function isWhitelisted(address account) public view returns (bool) { + return (whitelistAccountExpirations[account] > now); + } + /** * @notice Admin function for setting the voting delay * @param newVotingDelay new voting delay, in blocks @@ -315,6 +335,30 @@ contract GovernorBravoDelegate is GovernorBravoDelegateStorageV1, GovernorBravoE emit ProposalThresholdSet(oldProposalThreshold, proposalThreshold); } + /** + * @notice Admin function for setting the whitelist expiration as a timestamp for an account. Whitelist status allows accounts to propose without meeting threshold + * @param account Account address to set whitelist expiration for + * @param expiration Expiration for account whitelist status as timestamp (if now < expiration, whitelisted) + */ + function _setWhitelistAccountExpiration(address account, uint expiration) external { + require(msg.sender == admin || msg.sender == whitelistGuardian, "GovernorBravo::_setWhitelistAccountExpiration: admin only"); + whitelistAccountExpirations[account] = expiration; + + emit WhitelistAccountExpirationSet(account, expiration); + } + + /** + * @notice Admin function for setting the whitelistGuardian. WhitelistGuardian can cancel proposals from whitelisted addresses + * @param account Account to set whitelistGuardian to (0x0 to remove whitelistGuardian) + */ + function _setWhitelistGuardian(address account) external { + require(msg.sender == admin, "GovernorBravo::_setWhitelistGuardian: admin only"); + address oldGuardian = whitelistGuardian; + whitelistGuardian = account; + + emit WhitelistGuardianSet(oldGuardian, whitelistGuardian); + } + /** * @notice Initiate the GovernorBravo contract * @dev Admin only. Sets initial proposal id which initiates the contract, ensuring a continuous proposal id count diff --git a/contracts/Governance/GovernorBravoDelegateG1.sol b/contracts/Governance/GovernorBravoDelegateG1.sol new file mode 100644 index 000000000..3b06db622 --- /dev/null +++ b/contracts/Governance/GovernorBravoDelegateG1.sol @@ -0,0 +1,388 @@ +pragma solidity ^0.5.16; +pragma experimental ABIEncoderV2; + +import "./GovernorBravoInterfaces.sol"; + +contract GovernorBravoDelegate is GovernorBravoDelegateStorageV1, GovernorBravoEvents { + + /// @notice The name of this contract + string public constant name = "Compound Governor Bravo"; + + /// @notice The minimum setable proposal threshold + uint public constant MIN_PROPOSAL_THRESHOLD = 50000e18; // 50,000 Comp + + /// @notice The maximum setable proposal threshold + uint public constant MAX_PROPOSAL_THRESHOLD = 100000e18; //100,000 Comp + + /// @notice The minimum setable voting period + uint public constant MIN_VOTING_PERIOD = 5760; // About 24 hours + + /// @notice The max setable voting period + uint public constant MAX_VOTING_PERIOD = 80640; // About 2 weeks + + /// @notice The min setable voting delay + uint public constant MIN_VOTING_DELAY = 1; + + /// @notice The max setable voting delay + uint public constant MAX_VOTING_DELAY = 40320; // About 1 week + + /// @notice The number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed + uint public constant quorumVotes = 400000e18; // 400,000 = 4% of Comp + + /// @notice The maximum number of actions that can be included in a proposal + uint public constant proposalMaxOperations = 10; // 10 actions + + /// @notice The EIP-712 typehash for the contract's domain + bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); + + /// @notice The EIP-712 typehash for the ballot struct used by the contract + bytes32 public constant BALLOT_TYPEHASH = keccak256("Ballot(uint256 proposalId,uint8 support)"); + + /** + * @notice Used to initialize the contract during delegator contructor + * @param timelock_ The address of the Timelock + * @param comp_ The address of the COMP token + * @param votingPeriod_ The initial voting period + * @param votingDelay_ The initial voting delay + * @param proposalThreshold_ The initial proposal threshold + */ + function initialize(address timelock_, address comp_, uint votingPeriod_, uint votingDelay_, uint proposalThreshold_) public { + require(address(timelock) == address(0), "GovernorBravo::initialize: can only initialize once"); + require(msg.sender == admin, "GovernorBravo::initialize: admin only"); + require(timelock_ != address(0), "GovernorBravo::initialize: invalid timelock address"); + require(comp_ != address(0), "GovernorBravo::initialize: invalid comp address"); + require(votingPeriod_ >= MIN_VOTING_PERIOD && votingPeriod_ <= MAX_VOTING_PERIOD, "GovernorBravo::initialize: invalid voting period"); + require(votingDelay_ >= MIN_VOTING_DELAY && votingDelay_ <= MAX_VOTING_DELAY, "GovernorBravo::initialize: invalid voting delay"); + require(proposalThreshold_ >= MIN_PROPOSAL_THRESHOLD && proposalThreshold_ <= MAX_PROPOSAL_THRESHOLD, "GovernorBravo::initialize: invalid proposal threshold"); + + timelock = TimelockInterface(timelock_); + comp = CompInterface(comp_); + votingPeriod = votingPeriod_; + votingDelay = votingDelay_; + proposalThreshold = proposalThreshold_; + } + + /** + * @notice Function used to propose a new proposal. Sender must have delegates above the proposal threshold + * @param targets Target addresses for proposal calls + * @param values Eth values for proposal calls + * @param signatures Function signatures for proposal calls + * @param calldatas Calldatas for proposal calls + * @param description String description of the proposal + * @return Proposal id of new proposal + */ + function propose(address[] memory targets, uint[] memory values, string[] memory signatures, bytes[] memory calldatas, string memory description) public returns (uint) { + // Reject proposals before initiating as Governor + require(initialProposalId != 0, "GovernorBravo::propose: Governor Bravo not active"); + require(comp.getPriorVotes(msg.sender, sub256(block.number, 1)) > proposalThreshold, "GovernorBravo::propose: proposer votes below proposal threshold"); + require(targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length, "GovernorBravo::propose: proposal function information arity mismatch"); + require(targets.length != 0, "GovernorBravo::propose: must provide actions"); + require(targets.length <= proposalMaxOperations, "GovernorBravo::propose: too many actions"); + + uint latestProposalId = latestProposalIds[msg.sender]; + if (latestProposalId != 0) { + ProposalState proposersLatestProposalState = state(latestProposalId); + require(proposersLatestProposalState != ProposalState.Active, "GovernorBravo::propose: one live proposal per proposer, found an already active proposal"); + require(proposersLatestProposalState != ProposalState.Pending, "GovernorBravo::propose: one live proposal per proposer, found an already pending proposal"); + } + + uint startBlock = add256(block.number, votingDelay); + uint endBlock = add256(startBlock, votingPeriod); + + proposalCount++; + Proposal memory newProposal = Proposal({ + id: proposalCount, + proposer: msg.sender, + eta: 0, + targets: targets, + values: values, + signatures: signatures, + calldatas: calldatas, + startBlock: startBlock, + endBlock: endBlock, + forVotes: 0, + againstVotes: 0, + abstainVotes: 0, + canceled: false, + executed: false + }); + + proposals[newProposal.id] = newProposal; + latestProposalIds[newProposal.proposer] = newProposal.id; + + emit ProposalCreated(newProposal.id, msg.sender, targets, values, signatures, calldatas, startBlock, endBlock, description); + return newProposal.id; + } + + /** + * @notice Queues a proposal of state succeeded + * @param proposalId The id of the proposal to queue + */ + function queue(uint proposalId) external { + require(state(proposalId) == ProposalState.Succeeded, "GovernorBravo::queue: proposal can only be queued if it is succeeded"); + Proposal storage proposal = proposals[proposalId]; + uint eta = add256(block.timestamp, timelock.delay()); + for (uint i = 0; i < proposal.targets.length; i++) { + queueOrRevertInternal(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], eta); + } + proposal.eta = eta; + emit ProposalQueued(proposalId, eta); + } + + function queueOrRevertInternal(address target, uint value, string memory signature, bytes memory data, uint eta) internal { + require(!timelock.queuedTransactions(keccak256(abi.encode(target, value, signature, data, eta))), "GovernorBravo::queueOrRevertInternal: identical proposal action already queued at eta"); + timelock.queueTransaction(target, value, signature, data, eta); + } + + /** + * @notice Executes a queued proposal if eta has passed + * @param proposalId The id of the proposal to execute + */ + function execute(uint proposalId) external payable { + require(state(proposalId) == ProposalState.Queued, "GovernorBravo::execute: proposal can only be executed if it is queued"); + Proposal storage proposal = proposals[proposalId]; + proposal.executed = true; + for (uint i = 0; i < proposal.targets.length; i++) { + timelock.executeTransaction.value(proposal.values[i])(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], proposal.eta); + } + emit ProposalExecuted(proposalId); + } + + /** + * @notice Cancels a proposal only if sender is the proposer, or proposer delegates dropped below proposal threshold + * @param proposalId The id of the proposal to cancel + */ + function cancel(uint proposalId) external { + require(state(proposalId) != ProposalState.Executed, "GovernorBravo::cancel: cannot cancel executed proposal"); + + Proposal storage proposal = proposals[proposalId]; + require(msg.sender == proposal.proposer || comp.getPriorVotes(proposal.proposer, sub256(block.number, 1)) < proposalThreshold, "GovernorBravo::cancel: proposer above threshold"); + + proposal.canceled = true; + for (uint i = 0; i < proposal.targets.length; i++) { + timelock.cancelTransaction(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], proposal.eta); + } + + emit ProposalCanceled(proposalId); + } + + /** + * @notice Gets actions of a proposal + * @param proposalId the id of the proposal + * @return Targets, values, signatures, and calldatas of the proposal actions + */ + function getActions(uint proposalId) external view returns (address[] memory targets, uint[] memory values, string[] memory signatures, bytes[] memory calldatas) { + Proposal storage p = proposals[proposalId]; + return (p.targets, p.values, p.signatures, p.calldatas); + } + + /** + * @notice Gets the receipt for a voter on a given proposal + * @param proposalId the id of proposal + * @param voter The address of the voter + * @return The voting receipt + */ + function getReceipt(uint proposalId, address voter) external view returns (Receipt memory) { + return proposals[proposalId].receipts[voter]; + } + + /** + * @notice Gets the state of a proposal + * @param proposalId The id of the proposal + * @return Proposal state + */ + function state(uint proposalId) public view returns (ProposalState) { + require(proposalCount >= proposalId && proposalId > initialProposalId, "GovernorBravo::state: invalid proposal id"); + Proposal storage proposal = proposals[proposalId]; + if (proposal.canceled) { + return ProposalState.Canceled; + } else if (block.number <= proposal.startBlock) { + return ProposalState.Pending; + } else if (block.number <= proposal.endBlock) { + return ProposalState.Active; + } else if (proposal.forVotes <= proposal.againstVotes || proposal.forVotes < quorumVotes) { + return ProposalState.Defeated; + } else if (proposal.eta == 0) { + return ProposalState.Succeeded; + } else if (proposal.executed) { + return ProposalState.Executed; + } else if (block.timestamp >= add256(proposal.eta, timelock.GRACE_PERIOD())) { + return ProposalState.Expired; + } else { + return ProposalState.Queued; + } + } + + /** + * @notice Cast a vote for a proposal + * @param proposalId The id of the proposal to vote on + * @param support The support value for the vote. 0=against, 1=for, 2=abstain + */ + function castVote(uint proposalId, uint8 support) external { + emit VoteCast(msg.sender, proposalId, support, castVoteInternal(msg.sender, proposalId, support), ""); + } + + /** + * @notice Cast a vote for a proposal with a reason + * @param proposalId The id of the proposal to vote on + * @param support The support value for the vote. 0=against, 1=for, 2=abstain + * @param reason The reason given for the vote by the voter + */ + function castVoteWithReason(uint proposalId, uint8 support, string calldata reason) external { + emit VoteCast(msg.sender, proposalId, support, castVoteInternal(msg.sender, proposalId, support), reason); + } + + /** + * @notice Cast a vote for a proposal by signature + * @dev External function that accepts EIP-712 signatures for voting on proposals. + */ + function castVoteBySig(uint proposalId, uint8 support, uint8 v, bytes32 r, bytes32 s) external { + bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), getChainIdInternal(), address(this))); + bytes32 structHash = keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support)); + bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); + address signatory = ecrecover(digest, v, r, s); + require(signatory != address(0), "GovernorBravo::castVoteBySig: invalid signature"); + emit VoteCast(signatory, proposalId, support, castVoteInternal(signatory, proposalId, support), ""); + } + + /** + * @notice Internal function that caries out voting logic + * @param voter The voter that is casting their vote + * @param proposalId The id of the proposal to vote on + * @param support The support value for the vote. 0=against, 1=for, 2=abstain + * @return The number of votes cast + */ + function castVoteInternal(address voter, uint proposalId, uint8 support) internal returns (uint96) { + require(state(proposalId) == ProposalState.Active, "GovernorBravo::castVoteInternal: voting is closed"); + require(support <= 2, "GovernorBravo::castVoteInternal: invalid vote type"); + Proposal storage proposal = proposals[proposalId]; + Receipt storage receipt = proposal.receipts[voter]; + require(receipt.hasVoted == false, "GovernorBravo::castVoteInternal: voter already voted"); + uint96 votes = comp.getPriorVotes(voter, proposal.startBlock); + + if (support == 0) { + proposal.againstVotes = add256(proposal.againstVotes, votes); + } else if (support == 1) { + proposal.forVotes = add256(proposal.forVotes, votes); + } else if (support == 2) { + proposal.abstainVotes = add256(proposal.abstainVotes, votes); + } + + receipt.hasVoted = true; + receipt.support = support; + receipt.votes = votes; + + return votes; + } + + /** + * @notice Admin function for setting the voting delay + * @param newVotingDelay new voting delay, in blocks + */ + function _setVotingDelay(uint newVotingDelay) external { + require(msg.sender == admin, "GovernorBravo::_setVotingDelay: admin only"); + require(newVotingDelay >= MIN_VOTING_DELAY && newVotingDelay <= MAX_VOTING_DELAY, "GovernorBravo::_setVotingDelay: invalid voting delay"); + uint oldVotingDelay = votingDelay; + votingDelay = newVotingDelay; + + emit VotingDelaySet(oldVotingDelay,votingDelay); + } + + /** + * @notice Admin function for setting the voting period + * @param newVotingPeriod new voting period, in blocks + */ + function _setVotingPeriod(uint newVotingPeriod) external { + require(msg.sender == admin, "GovernorBravo::_setVotingPeriod: admin only"); + require(newVotingPeriod >= MIN_VOTING_PERIOD && newVotingPeriod <= MAX_VOTING_PERIOD, "GovernorBravo::_setVotingPeriod: invalid voting period"); + uint oldVotingPeriod = votingPeriod; + votingPeriod = newVotingPeriod; + + emit VotingPeriodSet(oldVotingPeriod, votingPeriod); + } + + /** + * @notice Admin function for setting the proposal threshold + * @dev newProposalThreshold must be greater than the hardcoded min + * @param newProposalThreshold new proposal threshold + */ + function _setProposalThreshold(uint newProposalThreshold) external { + require(msg.sender == admin, "GovernorBravo::_setProposalThreshold: admin only"); + require(newProposalThreshold >= MIN_PROPOSAL_THRESHOLD && newProposalThreshold <= MAX_PROPOSAL_THRESHOLD, "GovernorBravo::_setProposalThreshold: invalid proposal threshold"); + uint oldProposalThreshold = proposalThreshold; + proposalThreshold = newProposalThreshold; + + emit ProposalThresholdSet(oldProposalThreshold, proposalThreshold); + } + + /** + * @notice Initiate the GovernorBravo contract + * @dev Admin only. Sets initial proposal id which initiates the contract, ensuring a continuous proposal id count + * @param governorAlpha The address for the Governor to continue the proposal id count from + */ + function _initiate(address governorAlpha) external { + require(msg.sender == admin, "GovernorBravo::_initiate: admin only"); + require(initialProposalId == 0, "GovernorBravo::_initiate: can only initiate once"); + proposalCount = GovernorAlpha(governorAlpha).proposalCount(); + initialProposalId = proposalCount; + timelock.acceptAdmin(); + } + + /** + * @notice Begins transfer of admin rights. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer. + * @dev Admin function to begin change of admin. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer. + * @param newPendingAdmin New pending admin. + */ + function _setPendingAdmin(address newPendingAdmin) external { + // Check caller = admin + require(msg.sender == admin, "GovernorBravo:_setPendingAdmin: admin only"); + + // Save current value, if any, for inclusion in log + address oldPendingAdmin = pendingAdmin; + + // Store pendingAdmin with value newPendingAdmin + pendingAdmin = newPendingAdmin; + + // Emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin) + emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin); + } + + /** + * @notice Accepts transfer of admin rights. msg.sender must be pendingAdmin + * @dev Admin function for pending admin to accept role and update admin + */ + function _acceptAdmin() external { + // Check caller is pendingAdmin and pendingAdmin ≠ address(0) + require(msg.sender == pendingAdmin && msg.sender != address(0), "GovernorBravo:_acceptAdmin: pending admin only"); + + // Save current values for inclusion in log + address oldAdmin = admin; + address oldPendingAdmin = pendingAdmin; + + // Store admin with value pendingAdmin + admin = pendingAdmin; + + // Clear the pending value + pendingAdmin = address(0); + + emit NewAdmin(oldAdmin, admin); + emit NewPendingAdmin(oldPendingAdmin, pendingAdmin); + } + + function add256(uint256 a, uint256 b) internal pure returns (uint) { + uint c = a + b; + require(c >= a, "addition overflow"); + return c; + } + + function sub256(uint256 a, uint256 b) internal pure returns (uint) { + require(b <= a, "subtraction underflow"); + return a - b; + } + + function getChainIdInternal() internal pure returns (uint) { + uint chainId; + assembly { chainId := chainid() } + return chainId; + } +} \ No newline at end of file diff --git a/contracts/Governance/GovernorBravoInterfaces.sol b/contracts/Governance/GovernorBravoInterfaces.sol index d2b74f6f6..100fd72dd 100644 --- a/contracts/Governance/GovernorBravoInterfaces.sol +++ b/contracts/Governance/GovernorBravoInterfaces.sol @@ -40,6 +40,12 @@ contract GovernorBravoEvents { /// @notice Emitted when pendingAdmin is accepted, which means admin is updated event NewAdmin(address oldAdmin, address newAdmin); + + /// @notice Emitted when whitelist account expiration is set + event WhitelistAccountExpirationSet(address account, uint expiration); + + /// @notice Emitted when the whitelistGuardian is set + event WhitelistGuardianSet(address oldGuardian, address newGuardian); } contract GovernorBravoDelegatorStorage { @@ -162,6 +168,14 @@ contract GovernorBravoDelegateStorageV1 is GovernorBravoDelegatorStorage { } } +contract GovernorBravoDelegateStorageV2 is GovernorBravoDelegateStorageV1 { + /// @notice Stores the expiration of account whitelist status as a timestamp + mapping (address => uint) public whitelistAccountExpirations; + + /// @notice Address which manages whitelisted proposals and whitelist accounts + address public whitelistGuardian; +} + interface TimelockInterface { function delay() external view returns (uint); function GRACE_PERIOD() external view returns (uint); diff --git a/networks/mainnet.json b/networks/mainnet.json index 50d4d4f08..d0aafd5b9 100755 --- a/networks/mainnet.json +++ b/networks/mainnet.json @@ -1,4 +1,4 @@ -{ +{ "Contracts": { "AAVE": "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9", "cAAVEDelegate": "0xa035b9e130f2b1aedc733eefb1c67ba4c503491f", @@ -91,7 +91,10 @@ "CrowdProposalFactory": "0x54a06047087927D9B0fb21c1cf0ebd792764dDB8", "IRM_USDT_Updateable": "0xFB564da37B41b2F6B6EDcc3e56FbF523bD9F2012", "IRM_USDC_Updateable": "0xD8EC56013EA119E7181d231E5048f90fBbe753c0", - "IRM_UNI_Updateable": "0xd88b94128ff2b8cf2d7886cd1c1e46757418ca2a" + "IRM_UNI_Updateable": "0xd88b94128ff2b8cf2d7886cd1c1e46757418ca2a", + "GovernorBravoDelegator": "0xc0da02939e1441f497fd74f78ce7decb17b66529", + "GovernorBravoDelegateG1": "0xAAAaaAAAaaaa8FdB04F544F4EEe52939CddCe378", + "GovernorBravoDelegate": "0x563a63d650a5d259abae9248dddc6867813d3f87" }, "Blocks": { "cAAVEDelegate": 12654137, @@ -155,7 +158,10 @@ "CrowdProposalFactory": 12137801, "IRM_USDT_Updateable": 10609555, "IRM_USDC_Updateable": 10810554, - "IRM_UNI_Updateable": 11015612 + "IRM_UNI_Updateable": 11015612, + "GovernorBravoDelegator": 12006099, + "GovernorBravoDelegateG1": 12005518, + "GovernorBravoDelegate": 13170179 }, "PriceData": { "description": "Open Oracle Price Data", @@ -231,17 +237,29 @@ "address": "0xc00e94Cb662C3520282E6f5717214004A7f26888" }, "Governor": { - "GovernorBravo": { - "address": "0xc0da02939e1441f497fd74f78ce7decb17b66529", - "contract": "GovernorBravoDelegator", - "name": "GovernorBravo" - }, "GovernorAlpha": { "name": "GovernorAlpha", "contract": "GovernorAlpha", "address": "0xc0dA01a04C3f3E0be433606045bB7017A7323E38" } }, + "GovernorBravo": { + "GovernorBravoDelegateG1": { + "name": "GovernorBravoDelegate", + "contract": "GovernorBravoDelegate", + "address": "0xAAAaaAAAaaaa8FdB04F544F4EEe52939CddCe378" + }, + "GovernorBravoDelegator": { + "name": "GovernorBravoDelegator", + "contract": "GovernorBravoDelegator", + "address": "0xc0da02939e1441f497fd74f78ce7decb17b66529" + }, + "GovernorBravoDelegate": { + "name": "GovernorBravoDelegate", + "contract": "GovernorBravoDelegate", + "address": "0x563a63d650a5d259abae9248dddc6867813d3f87" + } + }, "Timelock": { "address": "0x6d903f6003cca6255D85CcA4D3B5E5146dC33925", "contract": "Timelock", @@ -305,7 +323,10 @@ "CrowdProposalFactory": "0x000000000000000000000000c00e94cb662c3520282e6f5717214004a7f26888000000000000000000000000c0da02939e1441f497fd74f78ce7decb17b665290000000000000000000000000000000000000000000000056bc75e2d63100000", "IRM_USDT_Updateable": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008e1bc9bf0400000000000000000000000000000000000000000000000000000f207539952d00000000000000000000000000000000000000000000000000000b1a2bc2ec5000000000000000000000000000006d903f6003cca6255d85cca4d3b5e5146dc33925", "IRM_USDC_Updateable": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008e1bc9bf0400000000000000000000000000000000000000000000000000000f207539952d00000000000000000000000000000000000000000000000000000b1a2bc2ec5000000000000000000000000000006d903f6003cca6255d85cca4d3b5e5146dc33925", - "IRM_UNI_Updateable": "?" + "IRM_UNI_Updateable": "?", + "GovernorBravoDelegate": "0x", + "GovernorBravoDelegateG1": "0x", + "GovernorBravoDelegator": "0x0000000000000000000000006d903f6003cca6255d85cca4d3b5e5146dc33925000000000000000000000000c00e94cb662c3520282e6f5717214004a7f268880000000000000000000000006d903f6003cca6255d85cca4d3b5e5146dc33925000000000000000000000000aaaaaaaaaaaa8fdb04f544f4eee52939cddce3780000000000000000000000000000000000000000000000000000000000004380000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000152d02c7e14af6800000" }, "Tokens": { "AAVE": { diff --git a/scenario/src/Contract/GovernorBravo.ts b/scenario/src/Contract/GovernorBravo.ts index f7e708607..5d0657ffd 100644 --- a/scenario/src/Contract/GovernorBravo.ts +++ b/scenario/src/Contract/GovernorBravo.ts @@ -48,9 +48,12 @@ export interface GovernorBravoMethods { proposalThreshold(): Callable; votingPeriod(): Callable; votingDelay(): Callable; + isWhitelisted(account: string): Callable; _setVotingDelay(newVotingDelay: encodedNumber): Sendable; _setVotingPeriod(newVotingPeriod: encodedNumber): Sendable; _setProposalThreshold(newProposalThreshold: encodedNumber): Sendable; + _setWhitelistAccountExpiration(account: string, expiration: encodedNumber): Sendable; + _setWhitelistGuardian(account: string): Sendable; _initiate(governorAlpha: string): Sendable; _initiate(): Sendable; _setImplementation(address: string): Sendable; diff --git a/scenario/src/Event/GovernorBravoEvent.ts b/scenario/src/Event/GovernorBravoEvent.ts index 0f448ecfd..b1a8d3247 100644 --- a/scenario/src/Event/GovernorBravoEvent.ts +++ b/scenario/src/Event/GovernorBravoEvent.ts @@ -168,6 +168,49 @@ async function setProposalThreshold( return world; } +async function setWhitelistAccountExpiration( + world: World, + from: string, + governor: GovernorBravo, + account: string, + expiration: NumberV +): Promise { + let invokation = await invoke( + world, + governor.methods._setWhitelistAccountExpiration(account,expiration.encode()), + from + ); + + world = addAction( + world, + `Set account whitelist expiration for ${account} to ${expiration.encode()}`, + invokation + ); + + return world; +} + +async function setWhitelistGuardian( + world: World, + from: string, + governor: GovernorBravo, + account: string +): Promise { + let invokation = await invoke( + world, + governor.methods._setWhitelistGuardian(account), + from + ); + + world = addAction( + world, + `Set whitelist guardian to ${account}`, + invokation + ); + + return world; +} + async function setImplementation( world: World, from: string, @@ -451,6 +494,39 @@ export function governorBravoCommands() { (world, from, { governor, newProposalThreshold }) => setProposalThreshold(world, from, governor, newProposalThreshold), { namePos: 1 } + ), + new Command<{ governor: GovernorBravo; address: AddressV, expiration: NumberV }>( + ` + #### SetWhitelistAccountExpiration + + * "GovernorBravo SetWhitelistAccountExpiration " - Sets whitelist account expiration (account can propose without proposal threshold until expiration) + * E.g. "GovernorBravo GovernorBravoScenario SetWhitelistAccountExpiration Arr00 1626387743" + `, + "SetWhitelistAccountExpiration", + [ + new Arg("governor", getGovernorV), + new Arg("address", getAddressV), + new Arg("expiration", getNumberV), + ], + (world, from, { governor, address, expiration }) => + setWhitelistAccountExpiration(world, from, governor, address.val, expiration), + { namePos: 1 } + ), + new Command<{ governor: GovernorBravo; address: AddressV, expiration: NumberV }>( + ` + #### SetWhitelistGuardian + + * "GovernorBravo SetWhitelistGuardian " - Sets whitelistGuardian account + * E.g. "GovernorBravo GovernorBravoScenario SetWhitelistGuardian Robert" + `, + "SetWhitelistGuardian", + [ + new Arg("governor", getGovernorV), + new Arg("address", getAddressV), + ], + (world, from, { governor, address, expiration }) => + setWhitelistGuardian(world, from, governor, address.val), + { namePos: 1 } ), new Command<{ governor: GovernorBravo; governorAlpha: AddressV }>( ` diff --git a/scenario/src/Value/GovernorBravoValue.ts b/scenario/src/Value/GovernorBravoValue.ts index 03995ee13..fd01f3d25 100644 --- a/scenario/src/Value/GovernorBravoValue.ts +++ b/scenario/src/Value/GovernorBravoValue.ts @@ -1,8 +1,8 @@ import { Event } from "../Event"; import { World } from "../World"; import { GovernorBravo } from "../Contract/GovernorBravo"; -import { getCoreValue, getEventV, mapValue } from "../CoreValue"; -import { AddressV, NumberV, EventV, Value } from "../Value"; +import { getCoreValue, getEventV, mapValue, getAddressV } from "../CoreValue"; +import { AddressV, NumberV, EventV, Value, BoolV } from "../Value"; import { Arg, Fetcher, getFetcherValue } from "../Command"; import { getProposalValue } from "./BravoProposalValue"; import { @@ -74,6 +74,14 @@ async function getVotingDelay( return new NumberV(await governor.methods.votingDelay().call()); } +async function getIsWhitelisted( + world: World, + governor: GovernorBravo, + account: string +): Promise { + return new BoolV(await governor.methods.isWhitelisted(account).call()); +} + export function governorBravoFetchers() { return [ new Fetcher<{ governor: GovernorBravo }, AddressV>( @@ -183,6 +191,20 @@ export function governorBravoFetchers() { getProposalValue(world, governor, params.val), { namePos: 1 } ), + + new Fetcher<{ governor: GovernorBravo; account: AddressV }, BoolV>( + ` + #### IsWhitelisted + + * "GovernorBravo IsWhitelisted " - Returns the whitelist status for a given account + * E.g. "GovernorBravo GovernorBravoScenario IsWhitelisted Jared" + `, + "IsWhitelisted", + [new Arg("governor", getGovernorV), new Arg("account", getAddressV)], + (world, { governor, account }) => + getIsWhitelisted(world, governor, account.val), + { namePos: 1 } + ), ]; } diff --git a/spec/scenario/GovernorBravo/WhitelistedAccount.scen b/spec/scenario/GovernorBravo/WhitelistedAccount.scen new file mode 100644 index 000000000..cfcb21f66 --- /dev/null +++ b/spec/scenario/GovernorBravo/WhitelistedAccount.scen @@ -0,0 +1,115 @@ +Macro DeployGov + SetBlockNumber 1 + Counter Deploy CNT1 + Timelock Deploy Scenario Jared 604800 + Comp Deploy Bank + GovernorBravo Deploy BravoDelegateHarness BravoDelegateHarness + GovernorBravo Deploy BravoDelegator LegitGov (Address Timelock) (Address Comp) (Address Root) (Address BravoDelegateHarness) 17280 1 100000e18 + GovernorBravo LegitGov MergeABI BravoDelegateHarness + GovernorBravo LegitGov HarnessInitiate + Timelock SetAdmin (Address LegitGov) + Enfranchise Root 200001e18 + Enfranchise Jared 200000e18 + +Macro Enfranchise user amount + From Bank (Comp Transfer user amount) + From user (Comp Delegate user) + +Macro GivenPendingProposal + DeployGov + MineBlock + MineBlock + GovernorBravo LegitGov Propose "Add and sub" [(Address CNT1) (Address CNT1)] [0 0] ["increment(uint256,uint256)" "decrement(uint256)"] [["7" "4"] ["2"]] + Assert Equal ("Pending") (GovernorBravo LegitGov Proposal LastProposal State) + +Macro GivenActiveProposal + GivenPendingProposal + MineBlock + MineBlock + Assert Equal ("Active") (GovernorBravo LegitGov Proposal LastProposal State) + +Macro GivenSucceededProposal + GivenActiveProposal + GovernorBravo LegitGov Proposal LastProposal Vote For + From Jared (GovernorBravo LegitGov Proposal LastProposal Vote For) + AdvanceBlocks 20000 + Assert Equal ("Succeeded") (GovernorBravo LegitGov Proposal LastProposal State) + +Macro GivenQueuedProposal + GivenSucceededProposal + FreezeTime 100 + GovernorBravo LegitGov Proposal LastProposal Queue + Assert Log ProposalQueued (id 2) + Assert Equal ("Queued") (GovernorBravo LegitGov Proposal LastProposal State) + +Macro GivenExecutedProposal + GivenQueuedProposal + FreezeTime 604901 + GovernorBravo LegitGov Proposal LastProposal Execute + Assert Equal ("Executed") (GovernorBravo LegitGov Proposal LastProposal State) + +Macro GivenWhitelistedUser user + SetTime Now + DeployGov + MineBlock + from root (GovernorBravo LegitGov SetWhitelistAccountExpiration (Address user) (FromNow 100000)) + Assert Equal (Comp GetCurrentVotes Robert) 0 + +Test "Cancel Whitelisted Proposal fail" + GivenWhitelistedUser Robert + Assert Equal (GovernorBravo LegitGov IsWhitelisted Robert) (True) + From Robert (GovernorBravo LegitGov Propose "Add and sub" [(Address CNT1) (Address CNT1)] [0 0] ["increment(uint256,uint256)" "decrement(uint256)"] [["7" "4"] ["2"]]) + AllowFailures + GovernorBravo LegitGov Proposal LastProposal Cancel + Assert Revert "revert GovernorBravo::cancel: whitelisted proposer" + +Test "Cancel Whitelisted Proposal passes after whitelist period" + GivenWhitelistedUser Robert + From Robert (GovernorBravo LegitGov Propose "Add and sub" [(Address CNT1) (Address CNT1)] [0 0] ["increment(uint256,uint256)" "decrement(uint256)"] [["7" "4"] ["2"]]) + IncreaseTime 100001 + GovernorBravo LegitGov Proposal LastProposal Cancel + +Test "Whitelisted Address can't propose after whitelist period" + GivenWhitelistedUser Robert + IncreaseTime 100001 + AllowFailures + From Robert (GovernorBravo LegitGov Propose "Add and sub" [(Address CNT1) (Address CNT1)] [0 0] ["increment(uint256,uint256)" "decrement(uint256)"] [["7" "4"] ["2"]]) + Assert Revert "revert GovernorBravo::propose: proposer votes below proposal threshold" + +Test "Proposer Can still cancel while whitelisted" + GivenWhitelistedUser Robert + From Robert (GovernorBravo LegitGov Propose "Add and sub" [(Address CNT1) (Address CNT1)] [0 0] ["increment(uint256,uint256)" "decrement(uint256)"] [["7" "4"] ["2"]]) + +Test "Only Whitelisted User gets whitelisted" + GivenWhitelistedUser Robert + AllowFailures + From Geoff (GovernorBravo LegitGov Propose "Add and sub" [(Address CNT1) (Address CNT1)] [0 0] ["increment(uint256,uint256)" "decrement(uint256)"] [["7" "4"] ["2"]]) + Assert Revert "revert GovernorBravo::propose: proposer votes below proposal threshold" + +Test "WhitelistGuardian can cancel proposal by whitelisted account" + GivenWhitelistedUser Robert + GovernorBravo LegitGov SetWhitelistGuardian Jared + From Robert (GovernorBravo LegitGov Propose "Add and sub" [(Address CNT1) (Address CNT1)] [0 0] ["increment(uint256,uint256)" "decrement(uint256)"] [["7" "4"] ["2"]]) + From Jared (GovernorBravo LegitGov Proposal LastProposal Cancel) + +Test "WhitelistGuardian can't cancel other proposals" + DeployGov + GovernorBravo LegitGov SetWhitelistGuardian Robert + From Jared (GovernorBravo LegitGov Propose "Add and sub" [(Address CNT1) (Address CNT1)] [0 0] ["increment(uint256,uint256)" "decrement(uint256)"] [["7" "4"] ["2"]]) + AllowFailures + From Robert (GovernorBravo LegitGov Proposal LastProposal Cancel) + Assert Revert "revert GovernorBravo::cancel: proposer above threshold" + +Test "WhitelistGuardian can't cancel from proposer above threshold" + GivenWhitelistedUser Jared + GovernorBravo LegitGov SetWhitelistGuardian Robert + From Jared (GovernorBravo LegitGov Propose "Add and sub" [(Address CNT1) (Address CNT1)] [0 0] ["increment(uint256,uint256)" "decrement(uint256)"] [["7" "4"] ["2"]]) + AllowFailures + From Robert (GovernorBravo LegitGov Proposal LastProposal Cancel) + Assert Revert "revert GovernorBravo::cancel: whitelisted proposer" + +Test "Set whitelist expiration from whitelist guardian" + DeployGov + GovernorBravo LegitGov SetWhitelistGuardian Robert + From Robert (GovernorBravo LegitGov SetWhitelistAccountExpiration (Address Geoff) (FromNow 100000)) + From Geoff (GovernorBravo LegitGov Propose "Add and sub" [(Address CNT1) (Address CNT1)] [0 0] ["increment(uint256,uint256)" "decrement(uint256)"] [["7" "4"] ["2"]]) diff --git a/spec/sim/0011-whitelist-accounts/hypothetical_upgrade.scen b/spec/sim/0011-whitelist-accounts/hypothetical_upgrade.scen new file mode 100755 index 000000000..349a7e155 --- /dev/null +++ b/spec/sim/0011-whitelist-accounts/hypothetical_upgrade.scen @@ -0,0 +1,40 @@ +#!/usr/bin/env yarn repl -s + +PrintTransactionLogs + +Alias Arr00 "0x2B384212EDc04Ae8bB41738D05BA20E33277bf33" +Alias CompHolder "0x7587cAefc8096f5F40ACB83A09Df031a018C66ec" +Alias CommunityMultisig "0xbbf3f1421D886E9b2c5D716B5192aC998af2012c" + +Web3Fork "https://mainnet-eth.compound.finance/@12835588" (CompHolder Arr00 CommunityMultisig) +UseConfigs mainnet + +GovernorBravo Deploy BravoDelegate BravoDelegate2 + +From CompHolder (Comp Delegate CompHolder) +From CompHolder (GovernorBravo GovernorBravoDelegator Propose "Whitelist Accounts" [(Address GovernorBravoDelegator) (Address GovernorBravoDelegator) (Address GovernorBravoDelegator)] [0 0 0] ["_setImplementation(address)" "_setWhitelistAccountExpiration(address,uint256)" "_setWhitelistGuardian(address)"] [[(Address BravoDelegate2)] [(Address Arr00) (FromNow 604911)] [(Address CommunityMultisig)]]) + +-- Vote for, queue, and execute the proposal +MineBlock +AdvanceBlocks 13140 +From CompHolder (GovernorBravo GovernorBravoDelegator Proposal LastProposal Vote For) +AdvanceBlocks 20000 +GovernorBravo GovernorBravoDelegator Proposal LastProposal Queue +IncreaseTime 604910 +GovernorBravo GovernorBravoDelegator Proposal LastProposal Execute +GovernorBravo GovernorBravoDelegator MergeABI BravoDelegate2 + +From Arr00 (GovernorBravo GovernorBravoDelegator Propose "Test Proposal" [(Address GovernorBravoDelegator)] [0] ["_setImplementation(address)"] [[(Address BravoDelegate2)]]) + +Send CommunityMultisig 1e18 + +-- Use community multisig +From CommunityMultisig (GovernorBravo GovernorBravoDelegator Proposal LastProposal Cancel) +From CommunityMultisig (GovernorBravo GovernorBravoDelegator SetWhitelistAccountExpiration (Address Arr00) 0) + +AllowFailures +From Arr00 (GovernorBravo GovernorBravoDelegator Propose "Test Proposal" [(Address GovernorBravoDelegator)] [0] ["_setImplementation(address)"] [[(Address BravoDelegate2)]]) +Assert Revert "revert GovernorBravo::propose: proposer votes below proposal threshold" + + +Print "Setup whitelisted accounts ok!" diff --git a/spec/sim/0011-whitelist-accounts/hypothetical_upgrade_post_deploy.scen b/spec/sim/0011-whitelist-accounts/hypothetical_upgrade_post_deploy.scen new file mode 100755 index 000000000..7800b5bb8 --- /dev/null +++ b/spec/sim/0011-whitelist-accounts/hypothetical_upgrade_post_deploy.scen @@ -0,0 +1,48 @@ +#!/usr/bin/env yarn repl -s + +PrintTransactionLogs + +Alias Arr00 "0x2B384212EDc04Ae8bB41738D05BA20E33277bf33" +Alias CompHolder "0x7587cAefc8096f5F40ACB83A09Df031a018C66ec" +Alias CommunityMultisig "0xbbf3f1421D886E9b2c5D716B5192aC998af2012c" +Alias NewBravoDelegate "0x563a63d650a5d259abae9248dddc6867813d3f87" + +Web3Fork "https://mainnet-eth.compound.finance/@13170219" (CompHolder Arr00 CommunityMultisig) +UseConfigs mainnet + +GovernorBravo Deploy BravoDelegate BravoDelegate2 -- Just used to merge abi + +From CompHolder (Comp Delegate CompHolder) +From CompHolder (GovernorBravo GovernorBravoDelegator Propose "Whitelist Accounts" [(Address GovernorBravoDelegator) (Address GovernorBravoDelegator)] [0 0] ["_setImplementation(address)" "_setWhitelistGuardian(address)"] [[(Address NewBravoDelegate)] [(Address CommunityMultisig)]]) + +-- Vote for, queue, and execute the proposal +MineBlock +AdvanceBlocks 13140 +From CompHolder (GovernorBravo GovernorBravoDelegator Proposal LastProposal Vote For) +AdvanceBlocks 20000 +GovernorBravo GovernorBravoDelegator Proposal LastProposal Queue +IncreaseTime 604910 +GovernorBravo GovernorBravoDelegator Proposal LastProposal Execute + +GovernorBravo GovernorBravoDelegator MergeABI BravoDelegate2 + +AllowFailures +From Arr00 (GovernorBravo GovernorBravoDelegator Propose "Test Proposal" [(Address GovernorBravoDelegator)] [0] ["_setImplementation(address)"] [[(Address NewBravoDelegate)]]) +Assert Revert "revert GovernorBravo::propose: proposer votes below proposal threshold" + +Successfully + +Send CommunityMultisig 1e18 +-- Use community multisig +Assert False (GovernorBravo GovernorBravoDelegator IsWhitelisted (Address Arr00)) +SetTime 1 +From CommunityMultisig (GovernorBravo GovernorBravoDelegator SetWhitelistAccountExpiration (Address Arr00) 1000) +Assert True (GovernorBravo GovernorBravoDelegator IsWhitelisted (Address Arr00)) + +From Arr00 (GovernorBravo GovernorBravoDelegator Propose "Test Proposal" [(Address GovernorBravoDelegator)] [0] ["_setImplementation(address)"] [[(Address NewBravoDelegate)]]) + +From CommunityMultisig (GovernorBravo GovernorBravoDelegator Proposal LastProposal Cancel) +Assert Equal ("Canceled") (GovernorBravo GovernorBravoDelegator Proposal LastProposal State) + + +Print "Setup whitelisted accounts ok!" diff --git a/spec/sim/0011-whitelist-accounts/hypothetical_upgrade_post_propose.scen b/spec/sim/0011-whitelist-accounts/hypothetical_upgrade_post_propose.scen new file mode 100755 index 000000000..42bf5dd87 --- /dev/null +++ b/spec/sim/0011-whitelist-accounts/hypothetical_upgrade_post_propose.scen @@ -0,0 +1,47 @@ +#!/usr/bin/env yarn repl -s + +PrintTransactionLogs + +Alias Arr00 "0x2B384212EDc04Ae8bB41738D05BA20E33277bf33" +Alias CompHolder "0xd5447a7aa223268398cf7c38c2c580622cc98772" +Alias CompHolder2 "0x6626593c237f530d15ae9980a95ef938ac15c35c" +Alias CommunityMultisig "0xbbf3f1421D886E9b2c5D716B5192aC998af2012c" +Alias NewBravoDelegate "0x563a63d650a5d259abae9248dddc6867813d3f87" + +Web3Fork "https://mainnet-eth.compound.finance/@13201186" (CompHolder CompHolder2 Arr00 CommunityMultisig) +UseConfigs mainnet + +GovernorBravo Deploy BravoDelegate BravoDelegate2 -- Just used to merge abi + +-- Vote for, queue, and execute the proposal +MineBlock +AdvanceBlocks 13140 +From CompHolder (GovernorBravo GovernorBravoDelegator Proposal LastProposal Vote For) +From CompHolder2 (GovernorBravo GovernorBravoDelegator Proposal LastProposal Vote For) +AdvanceBlocks 20000 +GovernorBravo GovernorBravoDelegator Proposal LastProposal Queue +IncreaseTime 604910 +GovernorBravo GovernorBravoDelegator Proposal LastProposal Execute + +GovernorBravo GovernorBravoDelegator MergeABI BravoDelegate2 + +AllowFailures +From Arr00 (GovernorBravo GovernorBravoDelegator Propose "Test Proposal" [(Address GovernorBravoDelegator)] [0] ["_setImplementation(address)"] [[(Address NewBravoDelegate)]]) +Assert Revert "revert GovernorBravo::propose: proposer votes below proposal threshold" + +Successfully + +Send CommunityMultisig 1e18 +-- Use community multisig +Assert False (GovernorBravo GovernorBravoDelegator IsWhitelisted (Address Arr00)) +SetTime 1 +From CommunityMultisig (GovernorBravo GovernorBravoDelegator SetWhitelistAccountExpiration (Address Arr00) 1000) +Assert True (GovernorBravo GovernorBravoDelegator IsWhitelisted (Address Arr00)) + +From Arr00 (GovernorBravo GovernorBravoDelegator Propose "Test Proposal" [(Address GovernorBravoDelegator)] [0] ["_setImplementation(address)"] [[(Address NewBravoDelegate)]]) + +From CommunityMultisig (GovernorBravo GovernorBravoDelegator Proposal LastProposal Cancel) +Assert Equal ("Canceled") (GovernorBravo GovernorBravoDelegator Proposal LastProposal State) + + +Print "Setup whitelisted accounts ok!"