Copy // SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;
import { Test } from "forge-std/Test.sol";
import { console2 } from "forge-std/console2.sol";
interface Timelock {
event DelayChanged(uint256 oldDelay, uint256 newDelay);
event PauserChanged(address oldPauser, address newPauser);
event TransactionCanceled(
bytes32 indexed txHash, address indexed target, bytes callData, uint256 indexed operationId
);
event TransactionExecuted(
bytes32 indexed txHash, address indexed target, bytes callData, uint256 indexed operationId
);
event TransactionQueued(
bytes32 indexed txHash, address indexed target, bytes callData, uint256 indexed operationId, uint256 lockedUntil
);
function ACCESS_MANAGER() external view returns (address);
function COMMUNITY_MULTISIG() external view returns (address);
function MINIMUM_DELAY() external view returns (uint256);
function OPERATIONS_MULTISIG() external view returns (address);
function cancelTransaction(address target, bytes memory callData, uint256 operationId) external;
function delay() external view returns (uint256);
function executeTransaction(address target, bytes memory callData, uint256 operationId)
external
returns (bool success, bytes memory returnData);
function pause(address[] memory targets) external;
function pauserMultisig() external view returns (address);
function queue(bytes32 transactionHash) external view returns (uint256 lockedUntil);
function queueTransaction(address target, bytes memory callData, uint256 operationId) external returns (bytes32);
function setDelay(uint256 newDelay) external;
function setPauser(address newPauser) external;
}
contract Bomb {
bool bombed;
function detonateBomb() external {
bombed = true;
}
function getBombed() external view returns(bool){
return bombed;
}
}
contract TimelockTest is Test {
string internal mainnetRpc = "XYZ";
address public timeLockAddr = 0x3C28B7c7Ba1A1f55c9Ce66b263B33B204f2126eA;
uint blockId;
Timelock timelock;
Bomb bombContract; // Sample Bomb contract intended to be detonated by OPERATIONS_MULTISIG via queue and execute
function setUp() public {
blockId = vm.createSelectFork(mainnetRpc);
timelock = Timelock(timeLockAddr);
bombContract = new Bomb();
}
function testFork() public {
//assert at initiation Bomb not detonated
bool blastedA = bombContract.getBombed();
assertEq(blastedA, false);
//address Msig
address OPERATIONS_MULTISIG = timelock.OPERATIONS_MULTISIG();
//make Tx for queue (intends to call detonateBomb())
address bombContractAddr = address(bombContract);
uint operationId = 444;
bytes memory KallData = abi.encodeCall(Bomb.detonateBomb, ());
//Queue Tx
vm.startPrank(OPERATIONS_MULTISIG);
bytes32 CorrectTxHash = timelock.queueTransaction(bombContractAddr,KallData,operationId);
//Cancels Tx wrongly (error prone, say input similar operationId)
uint errorInputedOperationId = 4444; // different from 444
bytes32 wrongHash = keccak256(abi.encode(bombContractAddr, KallData, errorInputedOperationId));
//Assert that wrongHash isn't the correct Txhash
assertTrue(wrongHash != CorrectTxHash );
//====Bug=Impact===Contract actually emits cacellation of a false TxHash
vm.expectEmit();
emit Timelock.TransactionCanceled(wrongHash,bombContractAddr,KallData,errorInputedOperationId);
timelock.cancelTransaction(bombContractAddr,KallData,/*The error*/errorInputedOperationId);
//====Bug=Impact===Intended cancelled Tx still valid. Assert TxHash is still queued && Valid
uint LockedUntil = timelock.queue(CorrectTxHash);
assertTrue(LockedUntil != 0);
//====Bug=Impact===Cancelled Tx / DEcision can still go throuh and detonate bomb
vm.warp(block.timestamp + timelock.delay());
(bool succes,) = timelock.executeTransaction(bombContractAddr,KallData,operationId);
bool blastedB = bombContract.getBombed();
assertEq(blastedB, true);
assertTrue(succes);
}
}