diff --git a/script/configs/holesky/M2_deploy_from_scratch.holesky.config.json b/script/configs/holesky/M2_deploy_from_scratch.holesky.config.json index d143bf043c..8cd29ce250 100644 --- a/script/configs/holesky/M2_deploy_from_scratch.holesky.config.json +++ b/script/configs/holesky/M2_deploy_from_scratch.holesky.config.json @@ -42,7 +42,7 @@ "init_paused_status": 0 }, "eigenPod": { - "MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR": 32000000000000000000, + "MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR": 32000000000, "GENESIS_TIME": 1695902400 }, "eigenPodManager": { diff --git a/script/configs/mainnet/M2_mainnet_upgrade.config.json b/script/configs/mainnet/M2_mainnet_upgrade.config.json new file mode 100644 index 0000000000..744672facd --- /dev/null +++ b/script/configs/mainnet/M2_mainnet_upgrade.config.json @@ -0,0 +1,46 @@ +{ + "chainInfo": { + "chainId": 1 + }, + "multisig_addresses": { + "communityMultisig": "0xFEA47018D632A77bA579846c840d5706705Dc598", + "executorMultisig": "0x369e6F597e22EaB55fFb173C6d9cD234BD699111", + "operationsMultisig": "0xBE1685C81aA44FF9FB319dD389addd9374383e90", + "pauserMultisig": "0x5050389572f2d220ad927CcbeA0D406831012390", + "timelock": "0xA6Db1A8C5a981d1536266D2a393c5F8dDb210EAF" + }, + "strategies": { + "numStrategies": 0, + "MAX_PER_DEPOSIT": 115792089237316195423570985008687907853269984665640564039457584007913129639935, + "MAX_TOTAL_DEPOSITS": 115792089237316195423570985008687907853269984665640564039457584007913129639935, + "strategiesToDeploy": [] + }, + "strategyManager": { + "init_strategy_whitelister": "0xBE1685C81aA44FF9FB319dD389addd9374383e90", + "init_paused_status": 1 + }, + "delegationManager": { + "init_paused_status": 0, + "init_minWithdrawalDelayBlocks": 50400 + }, + "avsDirectory": { + "init_paused_status": 0 + }, + "slasher": { + "init_paused_status": 115792089237316195423570985008687907853269984665640564039457584007913129639935 + }, + "eigenPod": { + "MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR": 32000000000, + "GENESIS_TIME": 1606824023 + }, + "eigenPodManager": { + "init_paused_status": 0, + "deneb_fork_timestamp": "1710338135" + }, + "delayedWithdrawalRouter": { + "init_paused_status": 0, + "init_withdrawalDelayBlocks": 50400 + }, + "ethPOSDepositAddress": "0x00000000219ab540356cBB839Cbe05303d7705Fa", + "beaconOracleAddress": "0x343907185b71aDF0eBa9567538314396aa985442" +} \ No newline at end of file diff --git a/script/deploy/mainnet/M2_Mainnet_Upgrade.s.sol b/script/deploy/mainnet/M2_Mainnet_Upgrade.s.sol new file mode 100644 index 0000000000..e052591bc7 --- /dev/null +++ b/script/deploy/mainnet/M2_Mainnet_Upgrade.s.sol @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import "../../utils/ExistingDeploymentParser.sol"; +import "../../utils/TimelockEncoding.sol"; +import "../../utils/Multisend.sol"; + +/** + * @notice Script used for the first deployment of EigenLayer core contracts to Holesky + * anvil --fork-url $RPC_MAINNET + * forge script script/deploy/mainnet/M2_Mainnet_Upgrade.s.sol:M2_Mainnet_Upgrade --rpc-url http://127.0.0.1:8545 --private-key $PRIVATE_KEY --broadcast -vvvv + * + * forge script script/deploy/mainnet/M2_Mainnet_Upgrade.s.sol:M2_Mainnet_Upgrade --rpc-url $RPC_MAINNET --private-key $PRIVATE_KEY --broadcast -vvvv + * + */ +contract M2_Mainnet_Upgrade is ExistingDeploymentParser { + function run() external virtual { + _parseDeployedContracts("script/output/mainnet/M1_deployment_mainnet_2023_6_9.json"); + _parseInitialDeploymentParams("script/configs/mainnet/M2_mainnet_upgrade.config.json"); + + // START RECORDING TRANSACTIONS FOR DEPLOYMENT + vm.startBroadcast(); + + emit log_named_address("Deployer Address", msg.sender); + + _deployImplementationContracts(); + + // STOP RECORDING TRANSACTIONS FOR DEPLOYMENT + vm.stopBroadcast(); + + // Simulate upgrade of contracts to new implementations + _simulateUpgrade(); + + // Sanity Checks + _verifyContractPointers(); + _verifyImplementations(); + _verifyContractsInitialized({isInitialDeployment: true}); + _verifyInitializationParams(); + + logAndOutputContractAddresses("script/output/mainnet/M2_mainnet_upgrade.output.json"); + } + + /** + * @notice Deploy EigenLayer contracts from scratch for Holesky + */ + function _deployImplementationContracts() internal { + // 1. Deploy New TUPS + avsDirectoryImplementation = new AVSDirectory(delegationManager); + avsDirectory = AVSDirectory( + address( + new TransparentUpgradeableProxy( + address(avsDirectoryImplementation), + address(eigenLayerProxyAdmin), + abi.encodeWithSelector( + AVSDirectory.initialize.selector, + executorMultisig, // initialOwner + eigenLayerPauserReg, + AVS_DIRECTORY_INIT_PAUSED_STATUS + ) + ) + ) + ); + + // 2. Deploy Implementations + eigenPodImplementation = new EigenPod( + IETHPOSDeposit(ETHPOSDepositAddress), + delayedWithdrawalRouter, + eigenPodManager, + EIGENPOD_MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, + EIGENPOD_GENESIS_TIME + ); + delegationManagerImplementation = new DelegationManager(strategyManager, slasher, eigenPodManager); + strategyManagerImplementation = new StrategyManager(delegationManager, eigenPodManager, slasher); + slasherImplementation = new Slasher(strategyManager, delegationManager); + eigenPodManagerImplementation = new EigenPodManager( + IETHPOSDeposit(ETHPOSDepositAddress), + eigenPodBeacon, + strategyManager, + slasher, + delegationManager + ); + delayedWithdrawalRouterImplementation = new DelayedWithdrawalRouter(eigenPodManager); + } + + function _simulateUpgrade() internal { + + vm.startPrank(executorMultisig); + + // First, upgrade the proxy contracts to point to the implementations + // AVSDirectory + // eigenLayerProxyAdmin.upgrade( + // TransparentUpgradeableProxy(payable(address(avsDirectory))), + // address(avsDirectoryImplementation) + // ); + // DelegationManager + eigenLayerProxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(address(delegationManager))), + address(delegationManagerImplementation) + ); + // StrategyManager + eigenLayerProxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(address(strategyManager))), + address(strategyManagerImplementation) + ); + // Slasher + eigenLayerProxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(address(slasher))), + address(slasherImplementation) + ); + // EigenPodManager + eigenLayerProxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(address(eigenPodManager))), + address(eigenPodManagerImplementation) + ); + // Delayed Withdrawal Router + eigenLayerProxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(address(delayedWithdrawalRouter))), + address(delayedWithdrawalRouterImplementation) + ); + + // Second, configure additional settings and paused statuses + delegationManager.setMinWithdrawalDelayBlocks(DELEGATION_MANAGER_MIN_WITHDRAWAL_DELAY_BLOCKS); + delegationManager.unpause(0); + eigenPodManager.unpause(0); + + eigenPodManager.setDenebForkTimestamp(EIGENPOD_MANAGER_DENEB_FORK_TIMESTAMP); + eigenPodManager.updateBeaconChainOracle(beaconOracle); + eigenPodBeacon.upgradeTo(address(eigenPodImplementation)); + + vm.stopPrank(); + } +} + +// forge t --mt test_queueUpgrade --fork-url $RPC_MAINNET -vvvv +contract Queue_M2_Upgrade is M2_Mainnet_Upgrade, TimelockEncoding { + Vm cheats = Vm(HEVM_ADDRESS); + + // Thurs Apr 04 2024 12:00:00 GMT-0700 (Pacific Daylight Time) + uint256 timelockEta = 1712214000; + + function test_queueUpgrade() external { + _parseDeployedContracts("script/output/mainnet/M1_deployment_mainnet_2023_6_9.json"); + _parseInitialDeploymentParams("script/configs/mainnet/M2_mainnet_upgrade.config.json"); + + // TODO: fill in correct addresses + // simulate deploying contracts + _deployImplementationContracts(); + + Tx[] memory txs = new Tx[](11); + // upgrade the DelegationManager, Slasher, StrategyManager, DelayedWithdrawalRouter, EigenPodManager, & EigenPod contracts + txs[0] = Tx( + address(eigenLayerProxyAdmin), + 0, + abi.encodeWithSelector( + ProxyAdmin.upgrade.selector, + TransparentUpgradeableProxy(payable(address(delegationManager))), + delegationManagerImplementation + ) + ); + + txs[1] = Tx( + address(eigenLayerProxyAdmin), + 0, + abi.encodeWithSelector( + ProxyAdmin.upgrade.selector, + TransparentUpgradeableProxy(payable(address(slasher))), + slasherImplementation + ) + ); + + txs[2] = Tx( + address(eigenLayerProxyAdmin), + 0, + abi.encodeWithSelector( + ProxyAdmin.upgrade.selector, + TransparentUpgradeableProxy(payable(address(strategyManager))), + strategyManagerImplementation + ) + ); + + txs[3] = Tx( + address(eigenLayerProxyAdmin), + 0, + abi.encodeWithSelector( + ProxyAdmin.upgrade.selector, + TransparentUpgradeableProxy(payable(address(delayedWithdrawalRouter))), + delayedWithdrawalRouterImplementation + ) + ); + + txs[4] = Tx( + address(eigenLayerProxyAdmin), + 0, + abi.encodeWithSelector( + ProxyAdmin.upgrade.selector, + TransparentUpgradeableProxy(payable(address(eigenPodManager))), + eigenPodManagerImplementation + ) + ); + + txs[5] = Tx( + address(eigenPodBeacon), + 0, + abi.encodeWithSelector( + UpgradeableBeacon.upgradeTo.selector, + eigenPodImplementation + ) + ); + + // set the min withdrawal delay blocks on the DelegationManager + txs[6] = Tx( + address(delegationManager), + 0, // value + abi.encodeWithSelector(DelegationManager.setMinWithdrawalDelayBlocks.selector, DELEGATION_MANAGER_MIN_WITHDRAWAL_DELAY_BLOCKS) + ); + + // set beacon chain oracle on EigenPodManager + txs[7] = Tx( + address(eigenPodManager), + 0, // value + abi.encodeWithSelector(EigenPodManager.updateBeaconChainOracle.selector, beaconOracle) + ); + + // set Deneb fork timestamp on EigenPodManager + txs[8] = Tx( + address(eigenPodManager), + 0, // value + abi.encodeWithSelector(EigenPodManager.setDenebForkTimestamp.selector, EIGENPOD_MANAGER_DENEB_FORK_TIMESTAMP) + ); + + // unpause everything on DelegationManager + txs[9] = Tx( + address(delegationManager), + 0, // value + abi.encodeWithSelector(Pausable.unpause.selector, 0) + ); + + // unpause everything on EigenPodManager + txs[10] = Tx( + address(eigenPodManager), + 0, // value + abi.encodeWithSelector(Pausable.unpause.selector, 0) + ); + + bytes memory calldata_to_multisend_contract = abi.encodeWithSelector(MultiSendCallOnly.multiSend.selector, encodeMultisendTxs(txs)); + emit log_named_bytes("calldata_to_multisend_contract", calldata_to_multisend_contract); + + bytes memory final_calldata_to_executor_multisig = encodeForExecutor({ + // call to executor will be from the timelock + from: timelock, + // performing many operations at the same time + to: multiSendCallOnly, + // value to send in tx + value: 0, + // calldata for the operation + data: calldata_to_multisend_contract, + // operation type (for performing many operations at the same time) + operation: ISafe.Operation.DelegateCall + }); + + (bytes memory calldata_to_timelock_queuing_action, bytes memory calldata_to_timelock_executing_action) = encodeForTimelock({ + // address to be called from the timelock + to: executorMultisig, + // value to send in tx + value: 0, + // calldata for the operation + data: final_calldata_to_executor_multisig, + // time at which the tx will become executable + timelockEta: timelockEta + + }); + + bytes32 expectedTxHash = getTxHash({ + target: executorMultisig, + _value: 0, + _data: final_calldata_to_executor_multisig, + eta: timelockEta + }); + emit log_named_bytes32("expectedTxHash", expectedTxHash); + + cheats.prank(operationsMultisig); + (bool success, ) = timelock.call(calldata_to_timelock_queuing_action); + require(success, "call to timelock queuing action failed"); + + require(ITimelock(timelock).queuedTransactions(expectedTxHash), "expectedTxHash not queued"); + + // test performing the upgrade + cheats.warp(timelockEta); + cheats.prank(operationsMultisig); + (success, ) = timelock.call(calldata_to_timelock_executing_action); + require(success, "call to timelock executing action failed"); + + // Check correctness after upgrade + _verifyContractPointers(); + _verifyImplementations(); + _verifyContractsInitialized({isInitialDeployment: true}); + _verifyInitializationParams(); + } + + function getTxHash(address target, uint256 _value, bytes memory _data, uint256 eta) public pure returns (bytes32) { + // empty bytes + bytes memory signature; + bytes32 txHash = keccak256(abi.encode(target, _value, signature, _data, eta)); + return txHash; + } +} diff --git a/script/output/mainnet/M1_deployment_mainnet_2023_6_9.json b/script/output/mainnet/M1_deployment_mainnet_2023_6_9.json index 0ec016f64e..89ea9e092c 100644 --- a/script/output/mainnet/M1_deployment_mainnet_2023_6_9.json +++ b/script/output/mainnet/M1_deployment_mainnet_2023_6_9.json @@ -1,6 +1,9 @@ { "addresses": { + "avsDirectory": "0x0000000000000000000000000000000000000000", + "avsDirectoryImplementation": "0x0000000000000000000000000000000000000000", "baseStrategyImplementation": "0xdfdA04f980bE6A64E3607c95Ca26012Ab9aA46d3", + "beaconOracleAddress": "0x0000000000000000000000000000000000000000", "delayedWithdrawalRouter": "0x7Fe7E9CC0F274d2435AD5d56D5fa73E47F6A23D8", "delayedWithdrawalRouterImplementation": "0x44Bcb0E01CD0C5060D4Bb1A07b42580EF983E2AF", "delegation": "0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A", diff --git a/script/utils/ExistingDeploymentParser.sol b/script/utils/ExistingDeploymentParser.sol index 0338687189..088c66b536 100644 --- a/script/utils/ExistingDeploymentParser.sol +++ b/script/utils/ExistingDeploymentParser.sol @@ -57,6 +57,7 @@ contract ExistingDeploymentParser is Script, Test { address operationsMultisig; address communityMultisig; address pauserMultisig; + address timelock; // strategies deployed StrategyBase[] public deployedStrategyArray; @@ -116,6 +117,7 @@ contract ExistingDeploymentParser is Script, Test { operationsMultisig = stdJson.readAddress(existingDeploymentData, ".parameters.operationsMultisig"); communityMultisig = stdJson.readAddress(existingDeploymentData, ".parameters.communityMultisig"); pauserMultisig = stdJson.readAddress(existingDeploymentData, ".parameters.pauserMultisig"); + timelock = stdJson.readAddress(existingDeploymentData, ".parameters.timelock"); eigenLayerProxyAdmin = ProxyAdmin( stdJson.readAddress(existingDeploymentData, ".addresses.eigenLayerProxyAdmin") @@ -475,8 +477,35 @@ contract ExistingDeploymentParser is Script, Test { eigenPodManager.paused() == EIGENPOD_MANAGER_INIT_PAUSED_STATUS, "eigenPodManager: init paused status set incorrectly" ); + require( + eigenPodManager.denebForkTimestamp() == EIGENPOD_MANAGER_DENEB_FORK_TIMESTAMP, + "eigenPodManager: denebForkTimestamp not set correctly" + ); + require( + eigenPodManager.beaconChainOracle() == beaconOracle, + "eigenPodManager: beaconChainOracle not set correctly" + ); + require( + eigenPodManager.ethPOS() == IETHPOSDeposit(ETHPOSDepositAddress), + "eigenPodManager: ethPOS not set correctly" + ); // EigenPodBeacon require(eigenPodBeacon.owner() == executorMultisig, "eigenPodBeacon: owner not set correctly"); + // EigenPodImplementation + require( + eigenPodImplementation.GENESIS_TIME() == EIGENPOD_GENESIS_TIME, + "eigenPodImplementation: GENESIS TIME not set correctly" + ); + require( + eigenPodImplementation.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR() == + EIGENPOD_MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR + && EIGENPOD_MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR % 1 gwei == 0, + "eigenPodImplementation: MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR not set correctly" + ); + require( + eigenPodImplementation.ethPOS() == IETHPOSDeposit(ETHPOSDepositAddress), + "eigenPodImplementation: ethPOS not set correctly" + ); // DelayedWithdrawalRouter require( delayedWithdrawalRouter.pauserRegistry() == eigenLayerPauserReg, diff --git a/script/utils/Multisend.sol b/script/utils/Multisend.sol new file mode 100644 index 0000000000..72637959a3 --- /dev/null +++ b/script/utils/Multisend.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.7.0 <0.9.0; + +/// @title Multi Send Call Only - Allows to batch multiple transactions into one, but only calls +/// @author Stefan George - +/// @author Richard Meissner - +/// @notice The guard logic is not required here as this contract doesn't support nested delegate calls +contract MultiSendCallOnly { + /// @dev Sends multiple transactions and reverts all if one fails. + /// @param transactions Encoded transactions. Each transaction is encoded as a packed bytes of + /// operation has to be uint8(0) in this version (=> 1 byte), + /// to as a address (=> 20 bytes), + /// value as a uint256 (=> 32 bytes), + /// data length as a uint256 (=> 32 bytes), + /// data as bytes. + /// see abi.encodePacked for more information on packed encoding + /// @notice The code is for most part the same as the normal MultiSend (to keep compatibility), + /// but reverts if a transaction tries to use a delegatecall. + /// @notice This method is payable as delegatecalls keep the msg.value from the previous call + /// If the calling method (e.g. execTransaction) received ETH this would revert otherwise + function multiSend(bytes memory transactions) public payable { + // solhint-disable-next-line no-inline-assembly + assembly { + let length := mload(transactions) + let i := 0x20 + for { + // Pre block is not used in "while mode" + } lt(i, length) { + // Post block is not used in "while mode" + } { + // First byte of the data is the operation. + // We shift by 248 bits (256 - 8 [operation byte]) it right since mload will always load 32 bytes (a word). + // This will also zero out unused data. + let operation := shr(0xf8, mload(add(transactions, i))) + // We offset the load address by 1 byte (operation byte) + // We shift it right by 96 bits (256 - 160 [20 address bytes]) to right-align the data and zero out unused data. + let to := shr(0x60, mload(add(transactions, add(i, 0x01)))) + // We offset the load address by 21 byte (operation byte + 20 address bytes) + let value := mload(add(transactions, add(i, 0x15))) + // We offset the load address by 53 byte (operation byte + 20 address bytes + 32 value bytes) + let dataLength := mload(add(transactions, add(i, 0x35))) + // We offset the load address by 85 byte (operation byte + 20 address bytes + 32 value bytes + 32 data length bytes) + let data := add(transactions, add(i, 0x55)) + let success := 0 + switch operation + case 0 { + success := call(gas(), to, value, data, dataLength, 0, 0) + } + // This version does not allow delegatecalls + case 1 { + revert(0, 0) + } + if eq(success, 0) { + revert(0, 0) + } + // Next entry starts at 85 byte + data length + i := add(i, add(0x55, dataLength)) + } + } + } +} \ No newline at end of file diff --git a/script/utils/TimelockEncoding.sol b/script/utils/TimelockEncoding.sol new file mode 100644 index 0000000000..f2a6c12e5c --- /dev/null +++ b/script/utils/TimelockEncoding.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import "forge-std/Script.sol"; +import "forge-std/Test.sol"; + +import "./TxEncodingInterfaces.sol"; + +contract TimelockEncoding is Test { + // CALLDATA FOR CALL FROM TIMELOCK TO EXECUTOR MULTISIG + uint256 safeTxGas = 0; + uint256 baseGas = 0; + uint256 gasPrice = 0; + address gasToken = address(uint160(0)); + address payable refundReceiver = payable(address(uint160(0))); + + // CALDATA FOR CALL TO TIMELOCK + uint256 timelockValue = 0; + // empty string, just encode all the data in 'timelockData' + string timelockSignature; + + // appropriate address on mainnet, Holesky, and many other chains + address multiSendCallOnly = 0x40A2aCCbd92BCA938b02010E17A5b8929b49130D; + + function encodeForTimelock( + address to, + uint256 value, + bytes memory data, + uint256 timelockEta + ) public returns (bytes memory calldata_to_timelock_queuing_action, bytes memory calldata_to_timelock_executing_action) { + calldata_to_timelock_queuing_action = abi.encodeWithSelector(ITimelock.queueTransaction.selector, + to, + value, + timelockSignature, + data, + timelockEta + ); + + emit log_named_bytes("calldata_to_timelock_queuing_action", calldata_to_timelock_queuing_action); + + calldata_to_timelock_executing_action = abi.encodeWithSelector(ITimelock.executeTransaction.selector, + to, + value, + timelockSignature, + data, + timelockEta + ); + + emit log_named_bytes("calldata_to_timelock_executing_action", calldata_to_timelock_executing_action); + + return (calldata_to_timelock_queuing_action, calldata_to_timelock_executing_action); + } + + function encodeForExecutor( + address from, + address to, + uint256 value, + bytes memory data, + ISafe.Operation operation + ) public returns (bytes memory) { + // encode the "signature" required by the Safe + bytes1 v = bytes1(uint8(1)); + bytes32 r = bytes32(uint256(uint160(from))); + bytes32 s; + bytes memory sig = abi.encodePacked(r,s,v); + emit log_named_bytes("sig", sig); + + bytes memory final_calldata_to_executor_multisig = abi.encodeWithSelector(ISafe.execTransaction.selector, + to, + value, + data, + operation, + safeTxGas, + baseGas, + gasPrice, + gasToken, + refundReceiver, + sig + ); + + emit log_named_bytes("final_calldata_to_executor_multisig", final_calldata_to_executor_multisig); + + return final_calldata_to_executor_multisig; + } + + struct Tx { + address to; + uint256 value; + bytes data; + } + + function encodeMultisendTxs(Tx[] memory txs) public pure returns (bytes memory) { + bytes memory ret = new bytes(0); + for (uint256 i = 0; i < txs.length; i++) { + ret = abi.encodePacked( + ret, + abi.encodePacked( + uint8(0), + txs[i].to, + txs[i].value, + uint256(txs[i].data.length), + txs[i].data + ) + ); + } + return ret; + } +} diff --git a/script/utils/TxEncodingInterfaces.sol b/script/utils/TxEncodingInterfaces.sol new file mode 100644 index 0000000000..a967da943b --- /dev/null +++ b/script/utils/TxEncodingInterfaces.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: LGPL AND BSD 3-Clause +pragma solidity >=0.5.0; + +// based on https://github.com/safe-global/safe-smart-account/blob/v1.3.0/contracts/GnosisSafe.sol +interface ISafe { + function execTransaction( + address to, + uint256 value, + bytes calldata data, + uint8 operation, + uint256 safeTxGas, + uint256 baseGas, + uint256 gasPrice, + address gasToken, + address payable refundReceiver, + bytes memory signatures + ) external; + + enum Operation {Call, DelegateCall} +} + +// based on https://github.com/compound-finance/compound-protocol/blob/master/contracts/Timelock.sol +interface ITimelock { + function queueTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) external returns (bytes32); + function executeTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) external payable returns (bytes memory); + function queuedTransactions(bytes32) external view returns (bool); +} +