Skip to content
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

L2 Governance #1991

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
498 changes: 498 additions & 0 deletions contracts/contracts/governance/L2Governance.sol

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions contracts/contracts/governance/L2Governor.sol
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)

Check warning on line 7 in contracts/contracts/governance/L2Governor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/L2Governor.sol#L7

Added line #L7 was not covered by tests
TimelockController(86400, proposers, executors)
{}
}
279 changes: 279 additions & 0 deletions contracts/contracts/governance/MainnetGovernanceExecutor.sol
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;

Check warning on line 63 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L62-L63

Added lines #L62 - L63 were not covered by tests
}

function initialize(

Check warning on line 66 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L66

Added line #L66 was not covered by tests
shahthepro marked this conversation as resolved.
Show resolved Hide resolved
uint64[] calldata chainSelectors,
address[] calldata l2Governances
) public initializer {
if (chainSelectors.length != l2Governances.length) {
revert InvalidInitializationArgLength();

Check warning on line 71 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L71

Added line #L71 was not covered by tests
}

for (uint256 i = 0; i < chainSelectors.length; ++i) {
shahthepro marked this conversation as resolved.
Show resolved Hide resolved
_addChainConfig(chainSelectors[i], l2Governances[i]);

Check warning on line 75 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L74-L75

Added lines #L74 - L75 were not covered by tests
}
}

/***************************************
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(

Check warning on line 89 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L89

Added line #L89 was not covered by tests

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary since you also have queueL2Proposal and cancelL2Proposal and for now L2Governance only accepts these two message types?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume you mean the sendCommandToL2 and not this internal function? I can get rid of that, yeah

Copy link

@pandadefi pandadefi Feb 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, commented at the wrong place.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have removed that sendCommandToL2. Also, removed the InvalidGovernanceCommand check and error since those are not user-input now

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);

Check warning on line 100 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L100

Added line #L100 was not covered by tests
}

ChainConfig memory config = chainConfig[chainSelector];

Check warning on line 103 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L103

Added line #L103 was not covered by tests

// Ensure it's a supported chain
if (!config.isSupported) {
revert UnsupportedChain(chainSelector);

Check warning on line 107 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L107

Added line #L107 was not covered by tests
}

// Build the command data
bytes memory data = abi.encode(

Check warning on line 111 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L111

Added line #L111 was not covered by tests
// Command Selector
commandSelector,
// Encoded Command Data
abi.encode(proposalId)
);

bytes memory extraArgs = hex"";

Check warning on line 118 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L118

Added line #L118 was not covered by tests

// Set gas limit if needed
if (maxGasLimit > 0) {
extraArgs = Client._argsToBytes(

Check warning on line 122 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L122

Added line #L122 was not covered by tests
// Set gas limit
Client.EVMExtraArgsV1({ gasLimit: maxGasLimit })
);
}

// Build the message
Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({

Check warning on line 129 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L129

Added line #L129 was not covered by tests
receiver: abi.encode(config.l2Governance),
data: data,
tokenAmounts: new Client.EVMTokenAmount[](0),
extraArgs: extraArgs,
feeToken: address(0)
});

IRouterClient router = IRouterClient(ccipRouter);

Check warning on line 137 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L137

Added line #L137 was not covered by tests

// Compute fees
uint256 fees = router.getFee(chainSelector, message);

Check warning on line 140 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L140

Added line #L140 was not covered by tests

Choose a reason for hiding this comment

The 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 getFee to query before sending a command to make sure you have enough ETH in the contract for execution.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have added a getCCIPFees method that takes the same params as _sendCommandToL2 but returns the fee amount


// Ensure the contract has enough balance to pay the fees
if (fees > address(this).balance) {
revert InsufficientBalanceForFees(fees);

Check warning on line 144 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L144

Added line #L144 was not covered by tests
}

// Forward to CCIP Router
// slither-disable-next-line arbitrary-send-eth
bytes32 messageId = router.ccipSend{ value: fees }(

Check warning on line 149 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L149

Added line #L149 was not covered by tests
chainSelector,
message
);

emit CommandSentToCCIPRouter(

Check warning on line 154 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L154

Added line #L154 was not covered by tests
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(

Check warning on line 170 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L170

Added line #L170 was not covered by tests
bytes2 commandSelector,
uint64 chainSelector,
uint256 proposalId,
uint256 maxGasLimit
) external onlyGovernor {
_sendCommandToL2(

Check warning on line 176 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L176

Added line #L176 was not covered by tests
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(

Check warning on line 191 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L191

Added line #L191 was not covered by tests
uint64 chainSelector,
uint256 proposalId,
uint256 maxGasLimit
) external onlyGovernor {
_sendCommandToL2(

Check warning on line 196 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L196

Added line #L196 was not covered by tests
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(

Check warning on line 211 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L211

Added line #L211 was not covered by tests
uint64 chainSelector,
uint256 proposalId,
uint256 maxGasLimit
) external onlyGovernor {
_sendCommandToL2(

Check warning on line 216 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L216

Added line #L216 was not covered by tests
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)

Check warning on line 233 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L233

Added line #L233 was not covered by tests
external
onlyGovernor
{
_addChainConfig(chainSelector, l2Governance);

Check warning on line 237 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L237

Added line #L237 was not covered by tests
}

function _addChainConfig(uint64 chainSelector, address l2Governance)

Check warning on line 240 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L240

Added line #L240 was not covered by tests
internal
{
if (chainConfig[chainSelector].isSupported) {
revert DuplicateChainConfig(chainSelector);

Check warning on line 244 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L244

Added line #L244 was not covered by tests
}

if (l2Governance == address(0)) {
revert InvalidGovernanceAddress();

Check warning on line 248 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L248

Added line #L248 was not covered by tests
}

chainConfig[chainSelector] = ChainConfig({

Check warning on line 251 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L251

Added line #L251 was not covered by tests
isSupported: true,
l2Governance: l2Governance
});

emit ChainConfigAdded(chainSelector, l2Governance);

Check warning on line 256 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L256

Added line #L256 was not covered by tests
}

/**
* @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);

Check warning on line 266 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L266

Added line #L266 was not covered by tests
}

chainConfig[chainSelector] = ChainConfig({

Check warning on line 269 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L269

Added line #L269 was not covered by tests
isSupported: false,
l2Governance: address(0)
});

emit ChainConfigRemoved(chainSelector);

Check warning on line 274 in contracts/contracts/governance/MainnetGovernanceExecutor.sol

View check run for this annotation

Codecov / codecov/patch

contracts/contracts/governance/MainnetGovernanceExecutor.sol#L274

Added line #L274 was not covered by tests
}

// Accept ETH
receive() external payable {}
}
16 changes: 16 additions & 0 deletions contracts/contracts/proxies/Proxies.sol
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,19 @@ contract OETHBuybackProxy is InitializeGovernedUpgradeabilityProxy {
contract BridgedWOETHProxy is InitializeGovernedUpgradeabilityProxy {

}

/**
* @notice L2GovernanceProxy delegates calls to L2Governance implementation
*/
contract L2GovernanceProxy is InitializeGovernedUpgradeabilityProxy {

}

/**
* @notice MainnetGovernanceExecutorProxy delegates calls to MainnetGovernanceExecutor implementation
*/
contract MainnetGovernanceExecutorProxy is
InitializeGovernedUpgradeabilityProxy
{

}
6 changes: 6 additions & 0 deletions contracts/contracts/utils/CCIPChainSelectors.sol
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;
31 changes: 31 additions & 0 deletions contracts/deploy/085_deploy_l2_governance_proxies.js
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;
Loading
Loading