test(governance): add timelock tests
This commit is contained in:
1 parent
aed4bd47bb
commit
92b1018b0a
1 file changed
+129
@@ -0,0 +1,129 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity 0.8.36;
|
||||||
|
|
||||||
|
import {Test} from "forge-std/Test.sol";
|
||||||
|
import {Timelock} from "../src/Timelock.sol";
|
||||||
|
|
||||||
|
contract Target {
|
||||||
|
address public immutable timelock;
|
||||||
|
uint256 public value;
|
||||||
|
|
||||||
|
constructor(address timelockAddress) {
|
||||||
|
timelock = timelockAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setValue(uint256 newValue) external {
|
||||||
|
require(msg.sender == timelock, "Target: FORBIDDEN");
|
||||||
|
value = newValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract TimelockTest is Test {
|
||||||
|
Timelock private timelock;
|
||||||
|
Target private target;
|
||||||
|
|
||||||
|
uint256 private constant delay = 2 days;
|
||||||
|
|
||||||
|
function setUp() public {
|
||||||
|
vm.warp(1_000_000);
|
||||||
|
timelock = new Timelock(address(this), delay);
|
||||||
|
target = new Target(address(timelock));
|
||||||
|
}
|
||||||
|
|
||||||
|
function queueSetValue(uint256 newValue, uint256 eta) private returns (bytes32) {
|
||||||
|
return timelock.queueTransaction(address(target), 0, "setValue(uint256)", abi.encode(newValue), eta);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testConstructorRejectsShortDelay() public {
|
||||||
|
vm.expectRevert(bytes("Timelock: DELAY_SHORT"));
|
||||||
|
new Timelock(address(this), 1 hours);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testQueueMarksTransaction() public {
|
||||||
|
uint256 eta = block.timestamp + delay;
|
||||||
|
bytes32 txHash = queueSetValue(42, eta);
|
||||||
|
assertTrue(timelock.queuedTransactions(txHash));
|
||||||
|
}
|
||||||
|
|
||||||
|
function testQueueRevertsWhenEtaTooSoon() public {
|
||||||
|
vm.expectRevert(bytes("Timelock: ETA_TOO_SOON"));
|
||||||
|
queueSetValue(42, block.timestamp + 1 hours);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testQueueOnlyAdmin() public {
|
||||||
|
vm.prank(address(0xBAD));
|
||||||
|
vm.expectRevert(bytes("Timelock: ONLY_ADMIN"));
|
||||||
|
queueSetValue(42, block.timestamp + delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testExecuteRevertsBeforeEta() public {
|
||||||
|
uint256 eta = block.timestamp + delay;
|
||||||
|
queueSetValue(42, eta);
|
||||||
|
vm.expectRevert(bytes("Timelock: NOT_READY"));
|
||||||
|
timelock.executeTransaction(address(target), 0, "setValue(uint256)", abi.encode(42), eta);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testExecuteAppliesEffectAfterDelay() public {
|
||||||
|
uint256 eta = block.timestamp + delay;
|
||||||
|
queueSetValue(42, eta);
|
||||||
|
vm.warp(eta);
|
||||||
|
timelock.executeTransaction(address(target), 0, "setValue(uint256)", abi.encode(42), eta);
|
||||||
|
assertEq(target.value(), 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testExecuteRevertsIfNotQueued() public {
|
||||||
|
uint256 eta = block.timestamp + delay;
|
||||||
|
vm.warp(eta);
|
||||||
|
vm.expectRevert(bytes("Timelock: NOT_QUEUED"));
|
||||||
|
timelock.executeTransaction(address(target), 0, "setValue(uint256)", abi.encode(42), eta);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testCancelPreventsExecution() public {
|
||||||
|
uint256 eta = block.timestamp + delay;
|
||||||
|
queueSetValue(42, eta);
|
||||||
|
timelock.cancelTransaction(address(target), 0, "setValue(uint256)", abi.encode(42), eta);
|
||||||
|
vm.warp(eta);
|
||||||
|
vm.expectRevert(bytes("Timelock: NOT_QUEUED"));
|
||||||
|
timelock.executeTransaction(address(target), 0, "setValue(uint256)", abi.encode(42), eta);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testExecuteRevertsAfterGracePeriod() public {
|
||||||
|
uint256 eta = block.timestamp + delay;
|
||||||
|
queueSetValue(42, eta);
|
||||||
|
vm.warp(eta + 14 days + 1);
|
||||||
|
vm.expectRevert(bytes("Timelock: STALE"));
|
||||||
|
timelock.executeTransaction(address(target), 0, "setValue(uint256)", abi.encode(42), eta);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testExecuteBubblesTargetRevert() public {
|
||||||
|
uint256 eta = block.timestamp + delay;
|
||||||
|
bytes memory badData = abi.encodeWithSignature("setValue(uint256)", uint256(7));
|
||||||
|
timelock.queueTransaction(address(target), 0, "", badData, eta);
|
||||||
|
vm.warp(eta);
|
||||||
|
vm.prank(address(0xBAD));
|
||||||
|
vm.expectRevert(bytes("Timelock: ONLY_ADMIN"));
|
||||||
|
timelock.executeTransaction(address(target), 0, "", badData, eta);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testSetDelayOnlyViaSelfCall() public {
|
||||||
|
vm.expectRevert(bytes("Timelock: ONLY_SELF"));
|
||||||
|
timelock.setDelay(3 days);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testTimelockCanRescheduleItselfThroughQueue() public {
|
||||||
|
uint256 eta = block.timestamp + delay;
|
||||||
|
timelock.queueTransaction(address(timelock), 0, "setDelay(uint256)", abi.encode(uint256(5 days)), eta);
|
||||||
|
vm.warp(eta);
|
||||||
|
timelock.executeTransaction(address(timelock), 0, "setDelay(uint256)", abi.encode(uint256(5 days)), eta);
|
||||||
|
assertEq(timelock.delay(), 5 days);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testFuzzExecuteNeverSucceedsBeforeEta(uint256 wait, uint256 newValue) public {
|
||||||
|
uint256 eta = block.timestamp + delay;
|
||||||
|
queueSetValue(newValue, eta);
|
||||||
|
wait = bound(wait, 0, delay - 1);
|
||||||
|
vm.warp(block.timestamp + wait);
|
||||||
|
vm.expectRevert(bytes("Timelock: NOT_READY"));
|
||||||
|
timelock.executeTransaction(address(target), 0, "setValue(uint256)", abi.encode(newValue), eta);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user