Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
574e0b8c
Unverified
Commit
574e0b8c
authored
Sep 08, 2023
by
Maurelian
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(ctb): Add vetoer and initiator roles
feat(ctb): Add getters to DelayedVetoable
parent
e76d230a
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
183 additions
and
11 deletions
+183
-11
.gas-snapshot
packages/contracts-bedrock/.gas-snapshot
+8
-1
DelayedVetoable.sol
packages/contracts-bedrock/src/universal/DelayedVetoable.sol
+85
-6
DelayedVetoable.t.sol
packages/contracts-bedrock/test/DelayedVetoable.t.sol
+90
-4
No files found.
packages/contracts-bedrock/.gas-snapshot
View file @
574e0b8c
...
@@ -46,7 +46,14 @@ CrossDomainOwnable3_Test:test_transferOwnership_zeroAddress_reverts() (gas: 1208
...
@@ -46,7 +46,14 @@ CrossDomainOwnable3_Test:test_transferOwnership_zeroAddress_reverts() (gas: 1208
CrossDomainOwnableThroughPortal_Test:test_depositTransaction_crossDomainOwner_succeeds() (gas: 81417)
CrossDomainOwnableThroughPortal_Test:test_depositTransaction_crossDomainOwner_succeeds() (gas: 81417)
CrossDomainOwnable_Test:test_onlyOwner_notOwner_reverts() (gas: 10597)
CrossDomainOwnable_Test:test_onlyOwner_notOwner_reverts() (gas: 10597)
CrossDomainOwnable_Test:test_onlyOwner_succeeds() (gas: 34883)
CrossDomainOwnable_Test:test_onlyOwner_succeeds() (gas: 34883)
DelayedVetoable_HandleCall_TestFail:test_handleCall_reverts() (gas: 24969)
DelayedVetoable_Getters_Test:test_getters() (gas: 28128)
DelayedVetoable_Getters_TestFail:test_getters_notVetoer() (gas: 52018)
DelayedVetoable_HandleCall_TestFail:test_handleCall_forwardingTargetReverts_reverts(bytes) (runs: 256, μ: 59701, ~: 59067)
DelayedVetoable_HandleCall_TestFail:test_handleCall_forwardingTooSoon_reverts(bytes) (runs: 256, μ: 49589, ~: 49170)
DelayedVetoable_HandleCall_TestFail:test_handleCall_targetIsZero_reverts() (gas: 17599)
DelayedVetoable_HandleCall_TestFail:test_handleCall_unAuthorizedInitiation_reverts() (gas: 6627)
DelayedVetoable_Veto_Test:test_veto_succeeds(bytes) (runs: 256, μ: 21411, ~: 20927)
DelayedVetoable_Veto_TestFail:test_veto_notVetoer_reverts() (gas: 17552)
DeleteOutput:test_script_succeeds() (gas: 3100)
DeleteOutput:test_script_succeeds() (gas: 3100)
DeployerWhitelist_Test:test_owner_succeeds() (gas: 7582)
DeployerWhitelist_Test:test_owner_succeeds() (gas: 7582)
DeployerWhitelist_Test:test_storageSlots_succeeds() (gas: 33395)
DeployerWhitelist_Test:test_storageSlots_succeeds() (gas: 33395)
...
...
packages/contracts-bedrock/src/universal/DelayedVetoable.sol
View file @
574e0b8c
...
@@ -11,6 +11,9 @@ contract DelayedVetoable {
...
@@ -11,6 +11,9 @@ contract DelayedVetoable {
/// @notice Error for the target is not set.
/// @notice Error for the target is not set.
error TargetUnitialized();
error TargetUnitialized();
/// @notice Error for unauthorized calls.
error Unauthorized(address expected, address actual);
/// @notice An event that is emitted when a call is initiated.
/// @notice An event that is emitted when a call is initiated.
/// @param callHash The hash of the call data.
/// @param callHash The hash of the call data.
/// @param data The data of the initiated call.
/// @param data The data of the initiated call.
...
@@ -21,22 +24,82 @@ contract DelayedVetoable {
...
@@ -21,22 +24,82 @@ contract DelayedVetoable {
/// @param data The data forwarded to the target.
/// @param data The data forwarded to the target.
event Forwarded(bytes32 indexed callHash, bytes data);
event Forwarded(bytes32 indexed callHash, bytes data);
/// @notice An event that is emitted each time a call is vetoed.
/// @param callHash The hash of the call data.
/// @param data The data forwarded to the target.
event Vetoed(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;
// TODO(maurelian): move this to the new SuperChainConfig contract
/// @notice The address that can veto a call.
address internal _vetoer;
// TODO(maurelian): move this to the new SuperChainConfig contract
/// @notice The address that can initiate a call.
address internal _initiator;
/// @notice The time that a call was initiated.
/// @notice The time that a call was initiated.
mapping(bytes32 => uint256) internal _queuedAt;
mapping(bytes32 => uint256) internal _queuedAt;
/// @notice The time to wait before forwarding a call.
/// @notice The time to wait before forwarding a call.
uint256 internal _delay;
uint256 internal _delay;
/// @notice A modifier that reverts if not called by the vetoer or by address(0) to allow
/// eth_call to interact with this proxy without needing to use low-level storage
/// inspection. We assume that nobody is able to trigger calls from address(0) during
/// normal EVM execution.
modifier handleCallIfNotVetoer() {
if (msg.sender == _vetoer || msg.sender == address(0)) {
_;
} else {
// This WILL halt the call frame on completion.
_handleCall();
}
}
/// @notice Sets the target admin during contract deployment.
/// @notice Sets the target admin during contract deployment.
/// @param target Address of the target.
/// @param vetoer_ Address of the vetoer.
constructor(address target, uint256 delay) {
/// @param initiator_ Address of the initiator.
_target = target;
/// @param target_ Address of the target.
_delay = delay;
/// @param delay_ Address of the delay.
constructor(address vetoer_, address initiator_, address target_, uint256 delay_) {
_vetoer = vetoer_;
_initiator = initiator_;
_target = target_;
_delay = delay_;
}
/// @notice Gets the initiator
/// @return Initiator address.
function initiator() external handleCallIfNotVetoer returns (address) {
return _initiator;
}
}
//// @notice Queries the vetoer address.
/// @return Vetoer address.
function vetoer() external handleCallIfNotVetoer returns (address) {
return _vetoer;
}
//// @notice Queries the target address.
/// @return Target address.
function target() external handleCallIfNotVetoer returns (address) {
return _target;
}
/// @notice Gets the delay
/// @return Delay address.
function delay() external handleCallIfNotVetoer returns (uint256) {
return _delay;
}
// TODO(maurelian): Remove this? The contract currently cannot handle forwarding ETH and I'm
// not sure the complexity is warranted.
// If we do allow it:
// 1. the callHash will need to include the value
// 2. forwarding will need to be done by passing the callHash, rather than the unhashed data
/// @notice Used when no data is passed to the contract.
/// @notice Used when no data is passed to the contract.
receive() external payable {
receive() external payable {
_handleCall();
_handleCall();
...
@@ -47,7 +110,18 @@ contract DelayedVetoable {
...
@@ -47,7 +110,18 @@ contract DelayedVetoable {
_handleCall();
_handleCall();
}
}
/// @notice Handles forwards the call to the target.
/// @notice Vetoes a call. This method can only be called by the vetoer. If called by another
/// address, execution will be redirected to _handleCall()
function veto(bytes memory data) external handleCallIfNotVetoer {
bytes32 callHash = keccak256(data);
delete _queuedAt[callHash];
emit Vetoed(callHash, data);
}
/// @notice Receives all calls other than those made by the vetoer.
/// This enables transparent initiation and forwarding of calls to the target and avoids
/// the need for additional layers of abi encoding.
function _handleCall() internal {
function _handleCall() internal {
if (_target == address(0)) {
if (_target == address(0)) {
revert TargetUnitialized();
revert TargetUnitialized();
...
@@ -55,13 +129,18 @@ contract DelayedVetoable {
...
@@ -55,13 +129,18 @@ contract DelayedVetoable {
bytes32 callHash = keccak256(msg.data);
bytes32 callHash = keccak256(msg.data);
if (_queuedAt[callHash] == 0) {
if (_queuedAt[callHash] == 0) {
if (msg.sender != _initiator) {
revert Unauthorized(_initiator, msg.sender);
}
_queuedAt[callHash] = block.timestamp;
_queuedAt[callHash] = block.timestamp;
emit Initiated(callHash, msg.data);
emit Initiated(callHash, msg.data);
} else if (_queuedAt[callHash] + _delay < block.timestamp) {
} else if (_queuedAt[callHash] + _delay < block.timestamp) {
// Not enough time has passed, so we'll revert.
// Not enough time has passed, so we'll revert.
revert ForwardingEarly();
revert ForwardingEarly();
} else {
} else {
// sufficient time has passed.
// The ability to finalize the call after sufficient time has passed does not require
// authorization.
// Delete the call to prevent replays
// Delete the call to prevent replays
delete _queuedAt[callHash];
delete _queuedAt[callHash];
...
...
packages/contracts-bedrock/test/DelayedVetoable.t.sol
View file @
574e0b8c
...
@@ -5,10 +5,17 @@ import { CommonTest, Reverter } from "./CommonTest.t.sol";
...
@@ -5,10 +5,17 @@ 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 {
error Unauthorized(address expected, address actual);
error ForwardingEarly();
error TargetUnitialized();
event Initiated(bytes32 indexed callHash, bytes data);
event Initiated(bytes32 indexed callHash, bytes data);
event Forwarded(bytes32 indexed callHash, bytes data);
event Forwarded(bytes32 indexed callHash, bytes data);
event Vetoed(bytes32 indexed callHash, bytes data);
address target = address(0xabba);
address target = address(0xabba);
address initiator = alice;
address vetoer = bob;
uint256 delay = 14 days;
uint256 delay = 14 days;
DelayedVetoable delayedVetoable;
DelayedVetoable delayedVetoable;
Reverter reverter;
Reverter reverter;
...
@@ -16,24 +23,53 @@ contract DelayedVetoable_Init is CommonTest {
...
@@ -16,24 +23,53 @@ contract DelayedVetoable_Init is CommonTest {
function setUp() public override {
function setUp() public override {
super.setUp();
super.setUp();
delayedVetoable = new DelayedVetoable({
delayedVetoable = new DelayedVetoable({
target: address(target),
initiator_: alice,
delay: delay
vetoer_: bob,
target_: address(target),
delay_: delay
});
});
reverter = new Reverter();
reverter = new Reverter();
}
}
}
}
contract DelayedVetoable_Getters_Test is DelayedVetoable_Init {
function test_getters() external {
vm.startPrank(address(0));
assertEq(delayedVetoable.initiator(), initiator);
assertEq(delayedVetoable.vetoer(), vetoer);
assertEq(delayedVetoable.target(), target);
assertEq(delayedVetoable.delay(), delay);
}
}
contract DelayedVetoable_Getters_TestFail is DelayedVetoable_Init {
function test_getters_notVetoer() external {
// getter calls from addresses other than the vetoer or zero address will revert in the
// initiation branch of the proxy.
vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, initiator, address(this)));
delayedVetoable.initiator();
vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, initiator, address(this)));
delayedVetoable.vetoer();
vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, initiator, address(this)));
delayedVetoable.target();
vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, initiator, address(this)));
delayedVetoable.delay();
}
}
contract DelayedVetoable_HandleCall_Test is DelayedVetoable_Init {
contract DelayedVetoable_HandleCall_Test is DelayedVetoable_Init {
function testFuzz_handleCall_initiation_succeeds(bytes memory data) external {
function testFuzz_handleCall_initiation_succeeds(bytes memory data) external {
vm.expectEmit(true, false, false, true, address(delayedVetoable));
vm.expectEmit(true, false, false, true, address(delayedVetoable));
emit Initiated(keccak256(data), data);
emit Initiated(keccak256(data), data);
vm.prank(initiator);
(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 {
function testFuzz_handleCall_forwarding_succeeds(bytes memory data) external {
// Initiate the call
// Initiate the call
vm.prank(initiator);
(bool success,) = address(delayedVetoable).call(data);
(bool success,) = address(delayedVetoable).call(data);
vm.warp(block.timestamp + delay);
vm.warp(block.timestamp + delay);
...
@@ -47,25 +83,75 @@ contract DelayedVetoable_HandleCall_Test is DelayedVetoable_Init {
...
@@ -47,25 +83,75 @@ contract DelayedVetoable_HandleCall_Test is DelayedVetoable_Init {
}
}
contract DelayedVetoable_HandleCall_TestFail is DelayedVetoable_Init {
contract DelayedVetoable_HandleCall_TestFail is DelayedVetoable_Init {
function test_handleCall_unAuthorizedInitiation_reverts() external {
vm.store(address(delayedVetoable), bytes32(0), bytes32(0));
vm.expectRevert(abi.encodeWithSelector(TargetUnitialized.selector));
(bool success,) = address(delayedVetoable).call(hex"");
assert(success);
}
function test_handleCall_targetIsZero_reverts() external {
vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, initiator, address(this)));
(bool success,) = address(delayedVetoable).call(hex"");
assert(success);
}
function test_handleCall_forwardingTooSoon_reverts(bytes memory data) external {
function test_handleCall_forwardingTooSoon_reverts(bytes memory data) external {
vm.prank(initiator);
(bool success,) = address(delayedVetoable).call(data);
(bool success,) = address(delayedVetoable).call(data);
vm.expectRevert();
vm.expectRevert(
abi.encodeWithSelector(ForwardingEarly.selector)
);
(success,) = address(delayedVetoable).call(data);
(success,) = address(delayedVetoable).call(data);
assertFalse(success);
assertFalse(success);
}
}
function test_handleCall_forwardingTwice_reverts(bytes memory data) external {
// Initiate the call
vm.prank(initiator);
(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);
// Attempt to foward the same call again.
vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, initiator, address(this)));
(success,) = address(delayedVetoable).call(data);
assert(success);
}
function test_handleCall_forwardingTargetReverts_reverts(bytes memory data) external {
function test_handleCall_forwardingTargetReverts_reverts(bytes memory data) external {
vm.etch(target, address(reverter).code);
vm.etch(target, address(reverter).code);
vm.prank(initiator);
(bool success,) = address(delayedVetoable).call(data);
(bool success,) = address(delayedVetoable).call(data);
vm.warp(block.timestamp + delay);
vm.warp(block.timestamp + delay);
vm.expectEmit(true, false, false, true, address(delayedVetoable));
vm.expectEmit(true, false, false, true, address(delayedVetoable));
emit Forwarded(keccak256(data), data);
emit Forwarded(keccak256(data), data);
vm.expectCall({ callee: target, data: data });
(success,) = address(delayedVetoable).call(data);
(success,) = address(delayedVetoable).call(data);
assertFalse(success);
assertFalse(success);
}
}
}
}
contract DelayedVetoable_Veto_Test is DelayedVetoable_Init {
function test_veto_succeeds(bytes memory data) external {
vm.expectEmit(true, false, false, true, address(delayedVetoable));
emit Vetoed(keccak256(data), data);
vm.prank(vetoer);
delayedVetoable.veto(data);
}
}
contract DelayedVetoable_Veto_TestFail is DelayedVetoable_Init {
function test_veto_notVetoer_reverts() external {
vm.expectRevert();
delayedVetoable.veto(hex"");
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment