github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/test/L1/DelayedVetoable.t.sol (about) 1 // SPDX-License-Identifier: MIT 2 pragma solidity 0.8.15; 3 4 import { Test } from "forge-std/Test.sol"; 5 import { DelayedVetoable } from "src/L1/DelayedVetoable.sol"; 6 7 contract DelayedVetoable_Init is Test { 8 error Unauthorized(address expected, address actual); 9 error ForwardingEarly(); 10 11 event Initiated(bytes32 indexed callHash, bytes data); 12 event Forwarded(bytes32 indexed callHash, bytes data); 13 event Vetoed(bytes32 indexed callHash, bytes data); 14 15 address target; 16 address initiator; 17 address vetoer; 18 uint256 operatingDelay = 14 days; 19 DelayedVetoable delayedVetoable; 20 21 function setUp() public { 22 initiator = makeAddr("initiator"); 23 vetoer = makeAddr("vetoer"); 24 target = makeAddr("target"); 25 vm.deal(initiator, 10000 ether); 26 vm.deal(vetoer, 10000 ether); 27 28 delayedVetoable = new DelayedVetoable({ 29 initiator_: initiator, 30 vetoer_: vetoer, 31 target_: address(target), 32 operatingDelay_: operatingDelay 33 }); 34 35 // Most tests will use the operating delay, so we call as the initiator with null data 36 // to set the delay. For tests that need to use the initial zero delay, we'll modify the 37 // value in storage. 38 vm.prank(initiator); 39 (bool success,) = address(delayedVetoable).call(hex""); 40 assertTrue(success); 41 } 42 43 /// @dev This function is used to prevent initiating the delay unintentionally. 44 /// It should only be used on tests prior to the delay being activated. 45 /// @param data The data to be used in the call. 46 function assumeNonzeroData(bytes memory data) internal pure { 47 vm.assume(data.length > 0); 48 } 49 50 /// @dev This function is used to ensure that the data does not clash with the queuedAt function selector. 51 /// @param data The data to be used in the call. 52 function assumeNoClash(bytes calldata data) internal pure { 53 if (data.length >= 4) { 54 vm.assume(bytes4(data[0:4]) != bytes4(keccak256("queuedAt(bytes32)"))); 55 } 56 } 57 } 58 59 contract DelayedVetoable_Getters_Test is DelayedVetoable_Init { 60 /// @dev The getters return the expected values when called by the zero address. 61 function test_getters() external { 62 vm.startPrank(address(0)); 63 assertEq(delayedVetoable.initiator(), initiator); 64 assertEq(delayedVetoable.vetoer(), vetoer); 65 assertEq(delayedVetoable.target(), target); 66 assertEq(delayedVetoable.delay(), operatingDelay); 67 assertEq(delayedVetoable.queuedAt(keccak256(abi.encode(0))), 0); 68 } 69 } 70 71 contract DelayedVetoable_Getters_TestFail is DelayedVetoable_Init { 72 /// @dev Check that getter calls from unauthorized entities will revert. 73 function test_getters_notZeroAddress_reverts() external { 74 vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, initiator, address(this))); 75 delayedVetoable.initiator(); 76 vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, initiator, address(this))); 77 delayedVetoable.vetoer(); 78 vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, initiator, address(this))); 79 delayedVetoable.target(); 80 vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, initiator, address(this))); 81 delayedVetoable.delay(); 82 vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector, initiator, address(this))); 83 delayedVetoable.queuedAt(keccak256(abi.encode(0))); 84 } 85 } 86 87 contract DelayedVetoable_HandleCall_Test is DelayedVetoable_Init { 88 /// @dev A call can be initiated by the initiator. 89 function testFuzz_handleCall_initiation_succeeds(bytes calldata data) external { 90 assumeNoClash(data); 91 vm.expectEmit(true, false, false, true, address(delayedVetoable)); 92 emit Initiated(keccak256(data), data); 93 94 vm.prank(initiator); 95 (bool success,) = address(delayedVetoable).call(data); 96 assertTrue(success); 97 } 98 99 /// @dev The delay is inititially set to zero and the call is immediately forwarded. 100 function testFuzz_handleCall_initialForwardingImmediately_succeeds( 101 bytes calldata inData, 102 bytes calldata outData 103 ) 104 external 105 { 106 assumeNonzeroData(inData); 107 assumeNoClash(inData); 108 109 // Reset the delay to zero 110 vm.store(address(delayedVetoable), bytes32(uint256(0)), bytes32(uint256(0))); 111 112 vm.mockCall(target, inData, outData); 113 vm.expectEmit(true, false, false, true, address(delayedVetoable)); 114 vm.expectCall({ callee: target, data: inData }); 115 emit Forwarded(keccak256(inData), inData); 116 vm.prank(initiator); 117 (bool success, bytes memory returnData) = address(delayedVetoable).call(inData); 118 assertTrue(success); 119 assertEq(returnData, outData); 120 121 // Check that the callHash is not stored for future forwarding 122 bytes32 callHash = keccak256(inData); 123 vm.prank(address(0)); 124 assertEq(delayedVetoable.queuedAt(callHash), 0); 125 } 126 127 /// @dev Calls are not forwarded until the delay has passed. 128 function testFuzz_handleCall_forwardingWithDelay_succeeds(bytes calldata data) external { 129 assumeNonzeroData(data); 130 assumeNoClash(data); 131 132 vm.prank(initiator); 133 (bool success,) = address(delayedVetoable).call(data); 134 135 // Check that the call is in the _queuedAt mapping 136 bytes32 callHash = keccak256(data); 137 vm.prank(address(0)); 138 assertEq(delayedVetoable.queuedAt(callHash), block.timestamp); 139 140 vm.warp(block.timestamp + operatingDelay); 141 vm.expectEmit(true, false, false, true, address(delayedVetoable)); 142 emit Forwarded(keccak256(data), data); 143 144 vm.expectCall({ callee: target, data: data }); 145 (success,) = address(delayedVetoable).call(data); 146 assertTrue(success); 147 } 148 } 149 150 contract DelayedVetoable_HandleCall_TestFail is DelayedVetoable_Init { 151 /// @dev Only the initiator can initiate a call. 152 function test_handleCall_unauthorizedInitiation_reverts() external { 153 vm.expectRevert(abi.encodeWithSelector(DelayedVetoable.Unauthorized.selector, initiator, address(this))); 154 (bool revertsAsExpected,) = address(delayedVetoable).call(hex"00001234"); 155 assertTrue(revertsAsExpected); 156 } 157 158 /// @dev The call cannot be forwarded until the delay has passed. 159 function testFuzz_handleCall_forwardingTooSoon_reverts(bytes calldata data) external { 160 assumeNoClash(data); 161 vm.prank(initiator); 162 (bool success,) = address(delayedVetoable).call(data); 163 assertTrue(success); 164 165 vm.expectRevert(DelayedVetoable.ForwardingEarly.selector); 166 (bool revertsAsExpected,) = address(delayedVetoable).call(data); 167 assertTrue(revertsAsExpected); 168 } 169 170 /// @dev The call cannot be forwarded a second time. 171 function testFuzz_handleCall_forwardingTwice_reverts(bytes calldata data) external { 172 assumeNoClash(data); 173 174 // Initiate the call 175 vm.prank(initiator); 176 (bool success,) = address(delayedVetoable).call(data); 177 assertTrue(success); 178 179 vm.warp(block.timestamp + operatingDelay); 180 vm.expectEmit(true, false, false, true, address(delayedVetoable)); 181 emit Forwarded(keccak256(data), data); 182 183 // Forward the call 184 vm.expectCall({ callee: target, data: data }); 185 (success,) = address(delayedVetoable).call(data); 186 assertTrue(success); 187 188 // Attempt to forward the same call again. 189 vm.expectRevert(abi.encodeWithSelector(DelayedVetoable.Unauthorized.selector, initiator, address(this))); 190 (bool revertsAsExpected,) = address(delayedVetoable).call(data); 191 assertTrue(revertsAsExpected); 192 } 193 194 /// @dev If the target reverts, it is bubbled up. 195 function testFuzz_handleCall_forwardingTargetReverts_reverts( 196 bytes calldata inData, 197 bytes calldata outData 198 ) 199 external 200 { 201 assumeNoClash(inData); 202 203 // Initiate the call 204 vm.prank(initiator); 205 (bool success,) = address(delayedVetoable).call(inData); 206 assertTrue(success); 207 208 vm.warp(block.timestamp + operatingDelay); 209 vm.expectEmit(true, false, false, true, address(delayedVetoable)); 210 emit Forwarded(keccak256(inData), inData); 211 212 vm.mockCallRevert(target, inData, outData); 213 214 // Forward the call 215 vm.expectRevert(outData); 216 (bool revertsAsExpected,) = address(delayedVetoable).call(inData); 217 assertTrue(revertsAsExpected); 218 } 219 220 function testFuzz_handleCall_forwardingTargetRetValue_succeeds( 221 bytes calldata inData, 222 bytes calldata outData 223 ) 224 external 225 { 226 assumeNoClash(inData); 227 228 // Initiate the call 229 vm.prank(initiator); 230 (bool success,) = address(delayedVetoable).call(inData); 231 assertTrue(success); 232 233 vm.warp(block.timestamp + operatingDelay); 234 vm.expectEmit(true, false, false, true, address(delayedVetoable)); 235 emit Forwarded(keccak256(inData), inData); 236 237 vm.mockCall(target, inData, outData); 238 239 // Forward the call 240 (bool success2, bytes memory retData) = address(delayedVetoable).call(inData); 241 assertTrue(success2); 242 assertEq(keccak256(retData), keccak256(outData)); 243 } 244 245 /// @dev A test documenting the single instance in which the contract is not 'transparent' to the initiator. 246 function testFuzz_handleCall_queuedAtClash_reverts() external { 247 // This will get us calldata with the same function selector as the queuedAt function, but 248 // with the incorrect input data length. 249 bytes memory inData = abi.encodePacked(keccak256("queuedAt(bytes32)")); 250 251 // Reset the delay to zero 252 vm.store(address(delayedVetoable), bytes32(uint256(0)), bytes32(uint256(0))); 253 254 vm.prank(initiator); 255 vm.expectRevert(bytes("")); 256 (bool revertsAsExpected,) = address(delayedVetoable).call(inData); 257 assertTrue(revertsAsExpected); 258 } 259 }