Files
fluxswap/test/Timelock.t.sol
T

130 lines
4.7 KiB
Solidity

// 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);
}
}