Commit e76d230a authored by Maurelian's avatar Maurelian

feat(ctb): Add the delay.

parent 2c7cfb80
......@@ -5,17 +5,36 @@ pragma solidity ^0.8.15;
import { console } from "forge-std/console.sol";
contract DelayedVetoable {
/// @notice Error for when attempting to forward too early.
error ForwardingEarly();
/// @notice Error for the target is not set.
error TargetUnitialized();
/// @notice An event that is emitted when a call is initiated.
/// @param callHash The hash of the call data.
/// @param data The data of the initiated call.
event Initiated(bytes32 indexed callHash, bytes data);
/// @notice An event that is emitted each time a call is forwarded.
/// @param data The address of the implementation contract
event Forwarded(bytes data);
/// @param callHash The hash of the call data.
/// @param data The data forwarded to the target.
event Forwarded(bytes32 indexed callHash, bytes data);
/// @notice The address that all calls are forwarded to after the delay.
address internal _target;
/// @notice The time that a call was initiated.
mapping(bytes32 => uint256) internal _queuedAt;
/// @notice The time to wait before forwarding a call.
uint256 internal _delay;
/// @notice Sets the target admin during contract deployment.
/// @param target Address of the target.
constructor(address target) {
constructor(address target, uint256 delay) {
_target = target;
_delay = delay;
}
/// @notice Used when no data is passed to the contract.
......@@ -30,16 +49,32 @@ contract DelayedVetoable {
/// @notice Handles forwards the call to the target.
function _handleCall() internal {
require(_target != address(0), "DelayedVetoable: target not initialized");
if (_target == address(0)) {
revert TargetUnitialized();
}
bytes32 callHash = keccak256(msg.data);
if (_queuedAt[callHash] == 0) {
_queuedAt[callHash] = block.timestamp;
emit Initiated(callHash, msg.data);
} else if (_queuedAt[callHash] + _delay < block.timestamp) {
// Not enough time has passed, so we'll revert.
revert ForwardingEarly();
} else {
// sufficient time has passed.
// Delete the call to prevent replays
delete _queuedAt[callHash];
emit Forwarded(msg.data);
(bool success,) = _target.call(msg.data);
assembly {
// Success == 0 means a revert. We'll revert too and pass the data up.
if iszero(success) { revert(0x0, returndatasize()) }
// Forward the call
emit Forwarded(callHash, msg.data);
(bool success,) = _target.call(msg.data);
assembly {
// Success == 0 means a revert. We'll revert too and pass the data up.
if iszero(success) { revert(0x0, returndatasize()) }
// Otherwise we'll just return and pass the data up.
return(0x0, returndatasize())
// Otherwise we'll just return and pass the data up.
return(0x0, returndatasize())
}
}
}
}
......@@ -5,38 +5,67 @@ import { CommonTest, Reverter } from "./CommonTest.t.sol";
import { DelayedVetoable } from "../src/universal/DelayedVetoable.sol";
contract DelayedVetoable_Init is CommonTest {
event Forwarded(bytes data);
event Initiated(bytes32 indexed callHash, bytes data);
event Forwarded(bytes32 indexed callHash, bytes data);
address target = address(0xabba);
uint256 delay = 14 days;
DelayedVetoable delayedVetoable;
Reverter reverter;
function setUp() public override {
super.setUp();
delayedVetoable = new DelayedVetoable({
target: address(target)
target: address(target),
delay: delay
});
reverter = new Reverter();
}
}
contract DelayedVetoable_HandleCall_Test is DelayedVetoable_Init {
function testFuzz_handleCall_succeeds(bytes memory data) external {
vm.expectCall(target, data);
function testFuzz_handleCall_initiation_succeeds(bytes memory data) external {
vm.expectEmit(true, false, false, true, address(delayedVetoable));
emit Forwarded(data);
emit Initiated(keccak256(data), data);
(bool success,) = address(delayedVetoable).call(data);
assert(success);
}
function testFuzz_handleCall_forwarding_succeeds(bytes memory data) external {
// Initiate the call
(bool success,) = address(delayedVetoable).call(data);
vm.warp(block.timestamp + delay);
vm.expectEmit(true, false, false, true, address(delayedVetoable));
emit Forwarded(keccak256(data), data);
vm.expectCall({ callee: target, data: data });
(success,) = address(delayedVetoable).call(data);
assert(success);
}
}
contract DelayedVetoable_HandleCall_TestFail is DelayedVetoable_Init {
function test_handleCall_reverts() external {
vm.expectCall(target, NON_ZERO_DATA);
function test_handleCall_forwardingTooSoon_reverts(bytes memory data) external {
(bool success,) = address(delayedVetoable).call(data);
vm.expectRevert();
// including data will call the fallback
(bool success,) = address(delayedVetoable).call(NON_ZERO_DATA);
(success,) = address(delayedVetoable).call(data);
assertFalse(success);
}
function test_handleCall_forwardingTargetReverts_reverts(bytes memory data) external {
vm.etch(target, address(reverter).code);
(bool success,) = address(delayedVetoable).call(data);
vm.warp(block.timestamp + delay);
vm.expectEmit(true, false, false, true, address(delayedVetoable));
emit Forwarded(keccak256(data), data);
vm.expectCall({ callee: target, data: data });
(success,) = address(delayedVetoable).call(data);
assertFalse(success);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment