Commit e76d230a authored by Maurelian's avatar Maurelian

feat(ctb): Add the delay.

parent 2c7cfb80
...@@ -5,17 +5,36 @@ pragma solidity ^0.8.15; ...@@ -5,17 +5,36 @@ pragma solidity ^0.8.15;
import { console } from "forge-std/console.sol"; import { console } from "forge-std/console.sol";
contract DelayedVetoable { 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. /// @notice An event that is emitted each time a call is forwarded.
/// @param data The address of the implementation contract /// @param callHash The hash of the call data.
event Forwarded(bytes 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. /// @notice The address that all calls are forwarded to after the delay.
address internal _target; 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. /// @notice Sets the target admin during contract deployment.
/// @param target Address of the target. /// @param target Address of the target.
constructor(address target) { constructor(address target, uint256 delay) {
_target = target; _target = target;
_delay = delay;
} }
/// @notice Used when no data is passed to the contract. /// @notice Used when no data is passed to the contract.
...@@ -30,16 +49,32 @@ contract DelayedVetoable { ...@@ -30,16 +49,32 @@ contract DelayedVetoable {
/// @notice Handles forwards the call to the target. /// @notice Handles forwards the call to the target.
function _handleCall() internal { 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); // Forward the call
(bool success,) = _target.call(msg.data); emit Forwarded(callHash, msg.data);
assembly { (bool success,) = _target.call(msg.data);
// Success == 0 means a revert. We'll revert too and pass the data up. assembly {
if iszero(success) { revert(0x0, returndatasize()) } // 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. // Otherwise we'll just return and pass the data up.
return(0x0, returndatasize()) return(0x0, returndatasize())
}
} }
} }
} }
...@@ -5,38 +5,67 @@ import { CommonTest, Reverter } from "./CommonTest.t.sol"; ...@@ -5,38 +5,67 @@ import { CommonTest, Reverter } from "./CommonTest.t.sol";
import { DelayedVetoable } from "../src/universal/DelayedVetoable.sol"; import { DelayedVetoable } from "../src/universal/DelayedVetoable.sol";
contract DelayedVetoable_Init is CommonTest { 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); address target = address(0xabba);
uint256 delay = 14 days;
DelayedVetoable delayedVetoable; DelayedVetoable delayedVetoable;
Reverter reverter; Reverter reverter;
function setUp() public override { function setUp() public override {
super.setUp(); super.setUp();
delayedVetoable = new DelayedVetoable({ delayedVetoable = new DelayedVetoable({
target: address(target) target: address(target),
delay: delay
}); });
reverter = new Reverter(); reverter = new Reverter();
} }
} }
contract DelayedVetoable_HandleCall_Test is DelayedVetoable_Init { contract DelayedVetoable_HandleCall_Test is DelayedVetoable_Init {
function testFuzz_handleCall_succeeds(bytes memory data) external { function testFuzz_handleCall_initiation_succeeds(bytes memory data) external {
vm.expectCall(target, data);
vm.expectEmit(true, false, false, true, address(delayedVetoable)); vm.expectEmit(true, false, false, true, address(delayedVetoable));
emit Forwarded(data); emit Initiated(keccak256(data), data);
(bool success,) = address(delayedVetoable).call(data); (bool success,) = address(delayedVetoable).call(data);
assert(success); 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 { contract DelayedVetoable_HandleCall_TestFail is DelayedVetoable_Init {
function test_handleCall_reverts() external { function test_handleCall_forwardingTooSoon_reverts(bytes memory data) external {
vm.expectCall(target, NON_ZERO_DATA); (bool success,) = address(delayedVetoable).call(data);
vm.expectRevert(); vm.expectRevert();
// including data will call the fallback (success,) = address(delayedVetoable).call(data);
(bool success,) = address(delayedVetoable).call(NON_ZERO_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); 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