Skip to content

Commit

Permalink
Fei DAO Timelock
Browse files Browse the repository at this point in the history
  • Loading branch information
Joey Santoro committed Oct 5, 2021
1 parent 6987b6a commit 1768e36
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 0 deletions.
42 changes: 42 additions & 0 deletions contracts/dao/FeiDAOTimelock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.4;

import "./Timelock.sol";
import "../refs/CoreRef.sol";

/**
@title Fei DAO Timelock
@notice Timelock with veto admin roles
@dev this timelock has the ability for the Guardian to pause queing or executing proposals, as well as being able to veto specific transactions.
The timelock itself could not unpause the timelock while in paused state.
*/
contract FeiDAOTimelock is Timelock, CoreRef {

constructor(address core_, address admin_, uint delay_, uint minDelay_)
Timelock(admin_, delay_, minDelay_)
CoreRef(core_)
{}

/// @notice queue a transaction, with pausability
function queueTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public override whenNotPaused returns (bytes32) {
return super.queueTransaction(target, value, signature, data, eta);
}

/// @notice veto a group of transactions
function vetoTransactions(address[] memory targets, uint[] memory values, string[] memory signatures, bytes[] memory datas, uint[] memory etas) public onlyGuardianOrGovernor {
for (uint i = 0; i < targets.length; i++) {
_cancelTransaction(targets[i], values[i], signatures[i], datas[i], etas[i]);
}
}

/// @notice execute a transaction, with pausability
function executeTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public override whenNotPaused payable returns (bytes memory) {
return super.executeTransaction(target, value, signature, data, eta);
}

/// @notice allow a governor to set a new pending timelock admin
function governorSetPendingAdmin(address newAdmin) public onlyGovernor {
pendingAdmin = newAdmin;
emit NewPendingAdmin(newAdmin);
}
}
7 changes: 7 additions & 0 deletions test/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ async function setNextBlockTimestamp(time: number) {
});
}

async function latestTime(): Promise<number> {
const { timestamp } = await ethers.provider.getBlock(await ethers.provider.getBlockNumber());

return timestamp as number;
}

async function mine() {
await hre.network.provider.request({
method: 'evm_mine'
Expand Down Expand Up @@ -140,6 +146,7 @@ export {
getCore,
getAddresses,
increaseTime,
latestTime,
expectApprox,
deployDevelopmentWeth,
getImpersonatedSigner,
Expand Down
77 changes: 77 additions & 0 deletions test/unit/dao/FeiDAOTimelock.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { expectRevert, getAddresses, getCore, getImpersonatedSigner, latestTime } from '../../helpers';
import { expect } from 'chai';
import { ethers } from 'hardhat';
import { Core, FeiDAOTimelock } from '@custom-types/contracts';
import { Signer } from '@ethersproject/abstract-signer';

describe.only('FeiDAOTimelock', function () {
let userAddress: string;
let guardianAddress: string;
let governorAddress: string;
let timelock: FeiDAOTimelock;
let userSigner: Signer;
let core: Core;
let delay: number;

beforeEach(async function () {
({
userAddress,
guardianAddress,
governorAddress,
} = await getAddresses());
core = await getCore();

delay = 1000;
timelock = await (
await ethers.getContractFactory('FeiDAOTimelock')
).deploy(core.address, userAddress, delay, delay);

userSigner = await getImpersonatedSigner(userAddress);

});

describe('Pausable', function () {
beforeEach(async function () {
await timelock.connect(await getImpersonatedSigner(governorAddress)).pause();
});

it('queue reverts', async function() {
const eta = (await latestTime()) + delay;
await expectRevert(timelock.connect(userSigner).queueTransaction(userAddress, 100, '', '0x', eta), 'Pausable: paused');
});

it('execute reverts', async function() {
const eta = (await latestTime()) + delay;
await expectRevert(timelock.connect(userSigner).executeTransaction(userAddress, 100, '', '0x', eta), 'Pausable: paused');
});
});

describe('Veto', function () {
it('non-governor or guardian reverts', async function() {
const eta = (await latestTime()) + delay;
await expectRevert(timelock.connect(userSigner).vetoTransactions([userAddress], [100], [''], ['0x'], [eta]), 'CoreRef: Caller is not a guardian or governor');
});

it('guardian succeeds', async function() {
const eta = (await latestTime()) + delay + delay;
await timelock.connect(userSigner).queueTransaction(userAddress, 100, '', '0x', eta);

const txHash = await timelock.getTxHash(userAddress, 100, '', '0x', eta);
expect(await timelock.queuedTransactions(txHash)).to.be.equal(true);

await timelock.connect(await getImpersonatedSigner(guardianAddress)).vetoTransactions([userAddress], [100], [''], ['0x'], [eta]);
expect(await timelock.queuedTransactions(txHash)).to.be.equal(false);
});

it('governor succeeds', async function() {
const eta = (await latestTime()) + delay + delay;
await timelock.connect(userSigner).queueTransaction(userAddress, 100, '', '0x', eta);

const txHash = await timelock.getTxHash(userAddress, 100, '', '0x', eta);
expect(await timelock.queuedTransactions(txHash)).to.be.equal(true);

await timelock.connect(await getImpersonatedSigner(governorAddress)).vetoTransactions([userAddress], [100], [''], ['0x'], [eta]);
expect(await timelock.queuedTransactions(txHash)).to.be.equal(false);
});
});
});

0 comments on commit 1768e36

Please sign in to comment.