-
Notifications
You must be signed in to change notification settings - Fork 85
L2 Governance #1991
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
L2 Governance #1991
Changes from all commits
345c972
d35f4d1
5739d39
df4feca
dfc3c2f
385fa8b
c529d00
b37fa4e
f7cd591
6ab40c3
1fb49bb
1805822
e569b9a
ee37a06
81e0891
71a30ef
edf9614
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import { TimelockController } from "@openzeppelin/contracts/governance/TimelockController.sol"; | ||
|
||
contract L2Governor is TimelockController { | ||
constructor( | ||
uint256 minDelay, | ||
address[] memory proposers, | ||
address[] memory executors | ||
) TimelockController(minDelay, proposers, executors) {} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,295 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import { Governable } from "./Governable.sol"; | ||
import { QUEUE_PROPOSAL_COMMAND, CANCEL_PROPOSAL_COMMAND } from "./L2Governance.sol"; | ||
import { Initializable } from "../utils/Initializable.sol"; | ||
|
||
import { ARBITRUM_ONE_SELECTOR } from "../utils/CCIPChainSelectors.sol"; | ||
|
||
import { Client } from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; | ||
import { IRouterClient } from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol"; | ||
|
||
contract MainnetGovernanceExecutor is Governable, Initializable { | ||
/*************************************** | ||
Events | ||
****************************************/ | ||
/** | ||
* @dev Emitted whenever a command is forwarded to CCIP Router | ||
*/ | ||
event CommandSentToCCIPRouter( | ||
uint64 indexed chainSelector, | ||
bytes32 messageId, | ||
bytes2 commandSelector, | ||
uint256 indexed proposalId | ||
); | ||
/** | ||
* @dev Emitted when a Chain Config is added | ||
*/ | ||
event ChainConfigAdded( | ||
uint64 indexed chainSelector, | ||
address indexed l2Governance | ||
); | ||
/** | ||
* @dev Emitted when a Chain Config is removed | ||
*/ | ||
event ChainConfigRemoved(uint64 indexed chainSelector); | ||
|
||
/*************************************** | ||
Errors | ||
****************************************/ | ||
error UnsupportedChain(uint64 chainSelector); | ||
error InsufficientBalanceForFees(uint256 feesRequired); | ||
error DuplicateChainConfig(uint64 chainSelector); | ||
error InvalidInitializationArgLength(); | ||
error InvalidGovernanceAddress(); | ||
|
||
/*************************************** | ||
Storage | ||
****************************************/ | ||
address public immutable ccipRouter; | ||
|
||
struct ChainConfig { | ||
bool isSupported; | ||
address l2Governance; | ||
} | ||
/** | ||
* @dev All supported chains | ||
*/ | ||
mapping(uint64 => ChainConfig) public chainConfig; | ||
|
||
constructor(address _ccipRouter) { | ||
ccipRouter = _ccipRouter; | ||
} | ||
|
||
function initialize( | ||
uint64[] calldata chainSelectors, | ||
address[] calldata l2Governances | ||
) external initializer { | ||
uint256 len = chainSelectors.length; | ||
if (len != l2Governances.length) { | ||
revert InvalidInitializationArgLength(); | ||
} | ||
|
||
for (uint256 i = 0; i < len; ++i) { | ||
_addChainConfig(chainSelectors[i], l2Governances[i]); | ||
} | ||
} | ||
|
||
/*************************************** | ||
CCIP | ||
****************************************/ | ||
/** | ||
* @dev Send a command to queue/cancel a L2 Proposal through CCIP Router | ||
* @param commandSelector Command to send | ||
* @param chainSelector Destination chain | ||
* @param proposalId L2 Proposal ID | ||
* @param maxGasLimit Max Gas Limit to use | ||
*/ | ||
function _sendCommandToL2( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this necessary since you also have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume you mean the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, commented at the wrong place. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have removed that |
||
bytes2 commandSelector, | ||
uint64 chainSelector, | ||
uint256 proposalId, | ||
uint256 maxGasLimit | ||
) internal { | ||
// Build the message | ||
Client.EVM2AnyMessage memory message = _buildCCIPMessage( | ||
commandSelector, | ||
chainSelector, | ||
proposalId, | ||
maxGasLimit | ||
); | ||
|
||
IRouterClient router = IRouterClient(ccipRouter); | ||
|
||
// Compute fees | ||
uint256 fees = router.getFee(chainSelector, message); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You might want to add a view function that returns the result of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have added a |
||
|
||
// Ensure the contract has enough balance to pay the fees | ||
if (fees > address(this).balance) { | ||
revert InsufficientBalanceForFees(fees); | ||
} | ||
|
||
// Forward to CCIP Router | ||
// slither-disable-next-line arbitrary-send-eth | ||
bytes32 messageId = router.ccipSend{ value: fees }( | ||
chainSelector, | ||
message | ||
); | ||
|
||
emit CommandSentToCCIPRouter( | ||
chainSelector, | ||
messageId, | ||
commandSelector, | ||
proposalId | ||
); | ||
} | ||
|
||
/** | ||
* @dev Returns the CCIP fees that the contract needs to execute the command | ||
* @param commandSelector Command to send | ||
* @param chainSelector Destination chain | ||
* @param proposalId L2 Proposal ID | ||
* @param maxGasLimit Max Gas Limit to use | ||
*/ | ||
function getCCIPFees( | ||
bytes2 commandSelector, | ||
uint64 chainSelector, | ||
uint256 proposalId, | ||
uint256 maxGasLimit | ||
) external view returns (uint256) { | ||
// Build the message | ||
Client.EVM2AnyMessage memory message = _buildCCIPMessage( | ||
commandSelector, | ||
chainSelector, | ||
proposalId, | ||
maxGasLimit | ||
); | ||
|
||
return IRouterClient(ccipRouter).getFee(chainSelector, message); | ||
} | ||
|
||
/** | ||
* @dev Builds the CCIP message for the given command | ||
* @param commandSelector Command to send | ||
* @param chainSelector Destination chain | ||
* @param proposalId L2 Proposal ID | ||
* @param maxGasLimit Max Gas Limit to use | ||
*/ | ||
function _buildCCIPMessage( | ||
bytes2 commandSelector, | ||
uint64 chainSelector, | ||
uint256 proposalId, | ||
uint256 maxGasLimit | ||
) internal view returns (Client.EVM2AnyMessage memory message) { | ||
ChainConfig memory config = chainConfig[chainSelector]; | ||
|
||
// Ensure it's a supported chain | ||
if (!config.isSupported) { | ||
revert UnsupportedChain(chainSelector); | ||
} | ||
|
||
// Build the command data | ||
bytes memory data = abi.encode( | ||
// Command Selector | ||
commandSelector, | ||
// Encoded Command Data | ||
abi.encode(proposalId) | ||
); | ||
|
||
bytes memory extraArgs = hex""; | ||
|
||
// Set gas limit if needed | ||
if (maxGasLimit > 0) { | ||
extraArgs = Client._argsToBytes( | ||
// Set gas limit | ||
Client.EVMExtraArgsV1({ gasLimit: maxGasLimit }) | ||
); | ||
} | ||
|
||
// Build the message | ||
message = Client.EVM2AnyMessage({ | ||
receiver: abi.encode(config.l2Governance), | ||
data: data, | ||
tokenAmounts: new Client.EVMTokenAmount[](0), | ||
extraArgs: extraArgs, | ||
feeToken: address(0) | ||
}); | ||
} | ||
|
||
/** | ||
* @dev Send a command to queue a L2 Proposal through CCIP Router. | ||
* Has to come through Governance | ||
* @param chainSelector Destination chain | ||
* @param proposalId L2 Proposal ID | ||
* @param maxGasLimit Max Gas Limit to use | ||
*/ | ||
function queueL2Proposal( | ||
uint64 chainSelector, | ||
uint256 proposalId, | ||
uint256 maxGasLimit | ||
) external payable onlyGovernor { | ||
_sendCommandToL2( | ||
QUEUE_PROPOSAL_COMMAND, | ||
chainSelector, | ||
proposalId, | ||
maxGasLimit | ||
); | ||
} | ||
|
||
/** | ||
* @dev Send a command to cancel a L2 Proposal through CCIP Router. | ||
* Has to come through Governance | ||
* @param chainSelector Destination chain | ||
* @param proposalId L2 Proposal ID | ||
* @param maxGasLimit Max Gas Limit to use | ||
*/ | ||
function cancelL2Proposal( | ||
uint64 chainSelector, | ||
uint256 proposalId, | ||
uint256 maxGasLimit | ||
) external payable onlyGovernor { | ||
_sendCommandToL2( | ||
CANCEL_PROPOSAL_COMMAND, | ||
chainSelector, | ||
proposalId, | ||
maxGasLimit | ||
); | ||
} | ||
|
||
/*************************************** | ||
Configuration | ||
****************************************/ | ||
/** | ||
* @dev Add a L2 Chain to forward commands to. | ||
* Has to go through Governance | ||
* @param chainSelector New timelock address | ||
* @param l2Governance New timelock address | ||
*/ | ||
function addChainConfig(uint64 chainSelector, address l2Governance) | ||
external | ||
onlyGovernor | ||
{ | ||
_addChainConfig(chainSelector, l2Governance); | ||
} | ||
|
||
function _addChainConfig(uint64 chainSelector, address l2Governance) | ||
internal | ||
{ | ||
if (chainConfig[chainSelector].isSupported) { | ||
revert DuplicateChainConfig(chainSelector); | ||
} | ||
|
||
if (l2Governance == address(0)) { | ||
revert InvalidGovernanceAddress(); | ||
} | ||
|
||
chainConfig[chainSelector] = ChainConfig({ | ||
isSupported: true, | ||
l2Governance: l2Governance | ||
}); | ||
|
||
emit ChainConfigAdded(chainSelector, l2Governance); | ||
} | ||
|
||
/** | ||
* @dev Remove a supported L2 chain. | ||
* Has to go through Governance | ||
* @param chainSelector New timelock address | ||
*/ | ||
function removeChainConfig(uint64 chainSelector) external onlyGovernor { | ||
if (!chainConfig[chainSelector].isSupported) { | ||
revert UnsupportedChain(chainSelector); | ||
} | ||
|
||
chainConfig[chainSelector] = ChainConfig({ | ||
isSupported: false, | ||
l2Governance: address(0) | ||
}); | ||
|
||
emit ChainConfigRemoved(chainSelector); | ||
} | ||
|
||
// Accept ETH | ||
receive() external payable {} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
interface ICCIPRouter { | ||
function getArmProxy() external view returns (address); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
interface ITimelockController { | ||
function isOperation(bytes32 id) external view returns (bool); | ||
|
||
function isOperationPending(bytes32 id) external view returns (bool); | ||
|
||
function isOperationReady(bytes32 id) external view returns (bool); | ||
|
||
function isOperationDone(bytes32 id) external view returns (bool); | ||
|
||
function getMinDelay() external view returns (uint256 duration); | ||
|
||
function hashOperationBatch( | ||
address[] calldata targets, | ||
uint256[] calldata values, | ||
bytes[] calldata datas, | ||
bytes32 predecessor, | ||
bytes32 salt | ||
) external pure returns (bytes32 hash); | ||
|
||
function scheduleBatch( | ||
address[] calldata targets, | ||
uint256[] calldata values, | ||
bytes[] calldata datas, | ||
bytes32 predecessor, | ||
bytes32 salt, | ||
uint256 delay | ||
) external; | ||
|
||
function executeBatch( | ||
address[] calldata targets, | ||
uint256[] calldata values, | ||
bytes[] calldata datas, | ||
bytes32 predecessor, | ||
bytes32 salt | ||
) external payable; | ||
|
||
function cancel(bytes32 id) external; | ||
} |
Uh oh!
There was an error while loading. Please reload this page.