-
Notifications
You must be signed in to change notification settings - Fork 88
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 7 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,10 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import { TimelockController } from "@openzeppelin/contracts/governance/TimelockController.sol"; | ||
|
||
contract L2Governor is TimelockController { | ||
constructor(address[] memory proposers, address[] memory executors) | ||
TimelockController(86400, proposers, executors) | ||
{} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,279 @@ | ||
// 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 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 InvalidGovernanceCommand(bytes2 command); | ||
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 | ||
) public initializer { | ||
if (chainSelectors.length != l2Governances.length) { | ||
revert InvalidInitializationArgLength(); | ||
} | ||
|
||
for (uint256 i = 0; i < chainSelectors.length; ++i) { | ||
shahthepro marked this conversation as resolved.
Show resolved
Hide resolved
|
||
_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 { | ||
// Ensure it's a valid command | ||
if ( | ||
commandSelector != QUEUE_PROPOSAL_COMMAND && | ||
commandSelector != CANCEL_PROPOSAL_COMMAND | ||
) { | ||
revert InvalidGovernanceCommand(commandSelector); | ||
} | ||
|
||
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 | ||
Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ | ||
receiver: abi.encode(config.l2Governance), | ||
data: data, | ||
tokenAmounts: new Client.EVMTokenAmount[](0), | ||
extraArgs: extraArgs, | ||
feeToken: address(0) | ||
}); | ||
|
||
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 Send a command to queue/cancel a L2 Proposal through CCIP Router. | ||
* Has to come through Governance | ||
* @param commandSelector Command to send | ||
* @param chainSelector Destination chain | ||
* @param proposalId L2 Proposal ID | ||
* @param maxGasLimit Max Gas Limit to use | ||
*/ | ||
function sendCommandToL2( | ||
bytes2 commandSelector, | ||
uint64 chainSelector, | ||
uint256 proposalId, | ||
uint256 maxGasLimit | ||
) external onlyGovernor { | ||
_sendCommandToL2( | ||
commandSelector, | ||
chainSelector, | ||
proposalId, | ||
maxGasLimit | ||
); | ||
} | ||
|
||
/** | ||
* @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 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 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; | ||
|
||
// Ref: https://docs.chain.link/ccip/supported-networks/v1_2_0/mainnet#arbitrum-mainnet | ||
uint64 constant MAINNET_SELECTOR = 5009297550715157269; | ||
uint64 constant ARBITRUM_ONE_SELECTOR = 4949039107694359620; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
const { isFork } = require("../test/helpers"); | ||
const { deployWithConfirmation } = require("../utils/deploy"); | ||
|
||
const deployName = "085_deploy_l2_governance_proxies"; | ||
|
||
const main = async (hre) => { | ||
console.log(`Running ${deployName} deployment on ${hre.network.name}...`); | ||
|
||
if (hre.network.name == "arbitrumOne") { | ||
// Deploy L2 Governor on Arbitrum One | ||
const l2GovernanceProxy = await deployWithConfirmation("L2GovernanceProxy"); | ||
console.log("L2GovernanceProxy address:", l2GovernanceProxy.address); | ||
} else if (hre.network.name == "mainnet") { | ||
// Deploy Governance Executor on Mainnet | ||
const mainnetGovernanceExecutorProxy = await deployWithConfirmation( | ||
"MainnetGovernanceExecutorProxy" | ||
); | ||
console.log( | ||
"MainnetGovernanceExecutorProxy address:", | ||
mainnetGovernanceExecutorProxy.address | ||
); | ||
} | ||
|
||
console.log(`${deployName} deploy done.`); | ||
}; | ||
|
||
main.id = deployName; | ||
main.skip = !(isFork || ["arbitrumOne", "mainnet"].includes(hre.network.name)); | ||
main.tags = ["arbitrum", "mainnet"]; | ||
|
||
module.exports = main; |
Uh oh!
There was an error while loading. Please reload this page.