Skip to content

Commit

Permalink
gov + timelock
Browse files Browse the repository at this point in the history
  • Loading branch information
clemsos committed May 21, 2024
1 parent bcb7286 commit 618a91e
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 22 deletions.
166 changes: 166 additions & 0 deletions smart-contracts/contracts/tokens/UP/UPGovernor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.21;

import "@openzeppelin/contracts-upgradeable5/governance/GovernorUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable5/governance/extensions/GovernorSettingsUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable5/governance/extensions/GovernorCountingSimpleUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable5/governance/extensions/GovernorVotesUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable5/governance/extensions/GovernorTimelockControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable5/proxy/utils/Initializable.sol";

/// @custom:security-contact hello@unlock-protocol.com
contract UPGovernor is
Initializable,
GovernorUpgradeable,
GovernorSettingsUpgradeable,
GovernorCountingSimpleUpgradeable,
GovernorVotesUpgradeable,
GovernorTimelockControlUpgradeable
{
uint private _quorum;

// add custom event for quorum changes
event QuorumSet(uint oldVotingDelay, uint newVotingDelay);

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

function initialize(
IVotes _token,
TimelockControllerUpgradeable _timelock
) public initializer {
__Governor_init("UnlockProtocolGovernor");
__GovernorSettings_init(43200 /* 6 day */, 43200 /* 6 days */, 0);
__GovernorCountingSimple_init();
__GovernorVotes_init(_token);
__GovernorTimelockControl_init(_timelock);

// default quorum set to 30k
_quorum = 30000e18;
}

// quorum set to 30k
function quorum(uint256) public view override returns (uint256) {
return _quorum;
}

// helper to change quorum
function setQuorum(uint256 newQuorum) public onlyGovernance {
uint256 oldQuorum = _quorum;
_quorum = newQuorum;
emit QuorumSet(oldQuorum, newQuorum);
}

// The following functions are overrides required by Solidity.

function votingDelay()
public
view
override(GovernorUpgradeable, GovernorSettingsUpgradeable)
returns (uint256)
{
return super.votingDelay();
}

function votingPeriod()
public
view
override(GovernorUpgradeable, GovernorSettingsUpgradeable)
returns (uint256)
{
return super.votingPeriod();
}

function state(
uint256 proposalId
)
public
view
override(GovernorUpgradeable, GovernorTimelockControlUpgradeable)
returns (ProposalState)
{
return super.state(proposalId);
}

function proposalNeedsQueuing(
uint256 proposalId
)
public
view
override(GovernorUpgradeable, GovernorTimelockControlUpgradeable)
returns (bool)
{
return super.proposalNeedsQueuing(proposalId);
}

function proposalThreshold()
public
view
override(GovernorUpgradeable, GovernorSettingsUpgradeable)
returns (uint256)
{
return super.proposalThreshold();
}

function _queueOperations(
uint256 proposalId,
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
)
internal
override(GovernorUpgradeable, GovernorTimelockControlUpgradeable)
returns (uint48)
{
return
super._queueOperations(
proposalId,
targets,
values,
calldatas,
descriptionHash
);
}

function _executeOperations(
uint256 proposalId,
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) internal override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) {
super._executeOperations(
proposalId,
targets,
values,
calldatas,
descriptionHash
);
}

function _cancel(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
)
internal
override(GovernorUpgradeable, GovernorTimelockControlUpgradeable)
returns (uint256)
{
return super._cancel(targets, values, calldatas, descriptionHash);
}

function _executor()
internal
view
override(GovernorUpgradeable, GovernorTimelockControlUpgradeable)
returns (address)
{
return super._executor();
}
}
14 changes: 14 additions & 0 deletions smart-contracts/contracts/tokens/UP/UPTimelock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

import "@openzeppelin/contracts-upgradeable/governance/TimelockControllerUpgradeable.sol";

contract UPTimelock is TimelockControllerUpgradeable {
function initialize(
uint256 minDelay,
address[] memory proposers,
address[] memory executors
) public initializer {
__TimelockController_init(minDelay, proposers, executors, msg.sender);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,17 @@ const { getEvent } = require('@unlock-protocol/hardhat-helpers')

const PROPOSER_ROLE = ethers.keccak256(ethers.toUtf8Bytes('PROPOSER_ROLE'))

describe('UnlockProtocolGovernor', () => {
// default values
const SIX_DAYS = 43200 // in blocks
const votingDelay = SIX_DAYS //
const votingPeriod = SIX_DAYS
const defaultQuorum = BigInt('30000') * BigInt(10 ** 18)

describe('UP Governor & Timelock', () => {
let gov
let udt
let updateTx

// default values
const votingDelay = 1
const votingPeriod = 45818
const defaultQuorum = ethers.parseEther('15000')

// helper to recreate voting process
const launchVotingProcess = async (voter, proposal) => {
const proposalTx = await gov.propose(...proposal)
Expand All @@ -35,7 +36,7 @@ describe('UnlockProtocolGovernor', () => {

// wait for a block (default voting delay)
const currentBlock = await ethers.provider.getBlockNumber()
await advanceBlockTo(currentBlock + 2)
await advanceBlock(BigInt(currentBlock + 1) + (await gov.votingDelay()))

// now ready to receive votes
assert.equal(await gov.state(proposalId), 1) // Active
Expand Down Expand Up @@ -73,26 +74,19 @@ describe('UnlockProtocolGovernor', () => {

beforeEach(async () => {
// deploying timelock with a proxy
const UnlockProtocolTimelock = await ethers.getContractFactory(
'UnlockProtocolTimelock'
)
const UPTimelock = await ethers.getContractFactory('UPTimelock')

const timelock = await upgrades.deployProxy(UnlockProtocolTimelock, [
const timelock = await upgrades.deployProxy(UPTimelock, [
1, // 1 second delay
[], // proposers list is empty at deployment
[ADDRESS_ZERO], // allow any address to execute a proposal once the timelock has expired
])

// deploy governor
const UnlockProtocolGovernor = await ethers.getContractFactory(
'UnlockProtocolGovernor'
)
const UPGovernor = await ethers.getContractFactory('UPGovernor')

gov = await upgrades.deployProxy(UnlockProtocolGovernor, [
gov = await upgrades.deployProxy(UPGovernor, [
await udt.getAddress(),
votingDelay,
votingPeriod,
defaultQuorum,
await timelock.getAddress(),
])

Expand All @@ -109,17 +103,17 @@ describe('UnlockProtocolGovernor', () => {
assert.equal(await gov.votingPeriod(), votingPeriod)
})

it('quorum is 15k UDT', async () => {
it('quorum is 30k UDT', async () => {
assert.equal(await gov.quorum(1), defaultQuorum)
})
})

describe('Update voting params', () => {
it('should only be possible through voting', async () => {
assert.equal(await gov.votingDelay(), votingDelay)
await reverts(gov.setVotingDelay(2), 'Governor: onlyGovernance')
await reverts(gov.setQuorum(2), 'Governor: onlyGovernance')
await reverts(gov.setVotingPeriod(2), 'Governor: onlyGovernance')
await reverts(gov.setVotingDelay(2), 'GovernorOnlyExecutor')
await reverts(gov.setQuorum(2), 'GovernorOnlyExecutor')
await reverts(gov.setVotingPeriod(2), 'GovernorOnlyExecutor')
})

beforeEach(async () => {
Expand Down

0 comments on commit 618a91e

Please sign in to comment.