-
Notifications
You must be signed in to change notification settings - Fork 99
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Joey Santoro
committed
Oct 5, 2021
1 parent
6987b6a
commit 1768e36
Showing
3 changed files
with
126 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); | ||
}); |