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

orbit chain governance deployment contracts #216

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions src/orbit-chain-governance/factories/GovernanceChainGovFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// SPDX-Licens©e-Identifier: Apache-2.0
pragma solidity 0.8.16;

import "../../L2ArbitrumGovernor.sol";
import "../../ArbitrumTimelock.sol";
import "../../UpgradeExecutor.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts-upgradeable/governance/utils/IVotesUpgradeable.sol";
import "@openzeppelin/contracts/utils/Address.sol";

/// @notice Parameters for deployment
struct DeployParams {
address _governanceToken; // Address of IVotesUpgradeable token deployed on the governance chain
address _govChainUpExec; // Address of governance UpgradeExecutor deployed on the governance chain
address _govChainProxyAdmin; // Address of ProxyAdmin deployed on the governance chain
uint256 _proposalThreshold; // Number of votes required to submit a proposal
uint256 _votingPeriod; // Time period in blocks during which voting for a proposal occurs
uint256 _votingDelay; // Delay in blocks after a proposal is submitted before voting begins
uint256 _minTimelockDelay; // Delay in seconds after proposal passes before it can be executed
uint64 _minPeriodAfterQuorum; // Minimum voting time in blocks after a quorum is reached
uint256 _coreQuorumThreshold; // Required quorum for proposal to pass; has 10k denominator
}
/// @title Factory that deploys governance chain contracts for cross chain governance
/// @notice Requires an UpgradeExecutor, a ProxyAdmin, and a governance token
/// that implements IVotesUpgradeable to already be deployed on the governance chain.
/// To be executed prior to ParentChainGovFactory on the parent chain.

contract GovernanceChainGovFactory is Ownable {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we add a test that deploys both of these contracts? Maybe we can run a modified verifier script against them too?

bool private done = false;

event Deployed(ArbitrumTimelock coreTimelock, L2ArbitrumGovernor coreGoverner);

error AlreadyExecuted();
error NotAContract(address _address);
error NotAGovernanceToken(address _address);

address immutable govLogic;
address immutable timelockLogic;

constructor() {
govLogic = address(new L2ArbitrumGovernor());
timelockLogic = address(new ArbitrumTimelock());
}

function deployStep1(DeployParams memory params)
Copy link
Collaborator

Choose a reason for hiding this comment

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

what if we did all of this in the constructor? (same w/ deploySetp2)

public
virtual
onlyOwner
returns (ArbitrumTimelock, L2ArbitrumGovernor)
{
if (done) {
revert AlreadyExecuted();
}
done = true;
// sanity checks:
// provided upgrade executor is a contract
if (!Address.isContract(params._govChainUpExec)) {
revert NotAContract(params._govChainUpExec);
}
// provided proxy admin is a contract
if (!Address.isContract(params._govChainProxyAdmin)) {
revert NotAContract(params._govChainProxyAdmin);
}
// provided governance token has an expected IVotesUpgradeable method
try IVotesUpgradeable(params._governanceToken).getPastTotalSupply(0) {}
catch (bytes memory) {
revert NotAGovernanceToken(params._governanceToken);
}
// end of sanity checks

// deploy and init the timelock
ArbitrumTimelock coreTimelock =
deployTimelock(ProxyAdmin(params._govChainProxyAdmin), timelockLogic);
coreTimelock.initialize(params._minTimelockDelay, new address[](0), new address[](0));

// deploy and init the core governor
L2ArbitrumGovernor coreGov =
deployGovernor(ProxyAdmin(params._govChainProxyAdmin), govLogic);
coreGov.initialize({
_token: IVotesUpgradeable(params._governanceToken),
_timelock: coreTimelock,
_owner: params._govChainUpExec,
_votingDelay: params._votingDelay,
_votingPeriod: params._votingPeriod,
_quorumNumerator: params._coreQuorumThreshold,
_proposalThreshold: params._proposalThreshold,
_minPeriodAfterQuorum: params._minPeriodAfterQuorum
});

// governor can submit proposals to timelock propose
coreTimelock.grantRole(coreTimelock.PROPOSER_ROLE(), address(coreGov));
// upgrade executor can cancel
coreTimelock.grantRole(coreTimelock.CANCELLER_ROLE(), params._govChainUpExec);

// anyone is allowed to execute on the timelock
coreTimelock.grantRole(coreTimelock.EXECUTOR_ROLE(), address(0));

// after initialisation, give admin roles to the upgrade executor
// and revoke admin roles from the timelock and from this deployer
coreTimelock.grantRole(coreTimelock.TIMELOCK_ADMIN_ROLE(), params._govChainUpExec);
coreTimelock.revokeRole(coreTimelock.TIMELOCK_ADMIN_ROLE(), address(coreTimelock));
coreTimelock.revokeRole(coreTimelock.TIMELOCK_ADMIN_ROLE(), address(this));

emit Deployed(coreTimelock, coreGov);
return (coreTimelock, coreGov);
}

function deployGovernor(ProxyAdmin _proxyAdmin, address _govChainGovernorLogic)
internal
returns (L2ArbitrumGovernor)
{
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
_govChainGovernorLogic,
address(_proxyAdmin),
bytes("")
);
return L2ArbitrumGovernor(payable(address(proxy)));
}

function deployTimelock(ProxyAdmin _proxyAdmin, address _govChainTimelockLogic)
internal
returns (ArbitrumTimelock)
{
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
_govChainTimelockLogic,
address(_proxyAdmin),
bytes("")
);
return ArbitrumTimelock(payable(address(proxy)));
}
}
79 changes: 79 additions & 0 deletions src/orbit-chain-governance/factories/ParentChainGovFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.16;

import "../../L1ArbitrumTimelock.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import "@arbitrum/nitro-contracts/src/bridge/IInbox.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Address.sol";

/// @title Factory that deploys governance chain contracts for cross chain governance
/// @notice Requires core nitro contracts (i.e., inbox), and Upgrade Executor, and a Proxy Admin to
/// be deployed on the parent chain, and a governance timelock be deployed on the governance chain
/// via GovernanceChainGovFactory.
contract ParentChainGovFactory is Ownable {
bool private done = false;

event Deployed(L1ArbitrumTimelock timelock, address inbox);

error AlreadyExecuted();
error NotAContract(address _address);

/// @param _parentChainUpExec address of UpgradeExecutor on parent chain
/// @param _parentChainProxyAdmin address of ProxyAdmin on parent chain
/// @param _inbox address of governance chain's inbox on parent chain
/// @param _governanceChainCoreTimelock address of core timelock on governance chain
/// @param _minTimelockDelay time in seconds after governance-initiated child-to-parent message is executed in the outbox before it can be executed in the timelock
function deployStep2(
address _parentChainUpExec,
address _parentChainProxyAdmin,
address _inbox,
address _governanceChainCoreTimelock,
uint256 _minTimelockDelay
) external onlyOwner returns (L1ArbitrumTimelock timelock) {
if (done) {
revert AlreadyExecuted();
}
done = true;
// sanity checks
if (!Address.isContract(_parentChainUpExec)) {
revert NotAContract(_parentChainUpExec);
}
if (!Address.isContract(_parentChainProxyAdmin)) {
revert NotAContract(_parentChainProxyAdmin);
}
if (!Address.isContract(_inbox)) {
revert NotAContract(_inbox);
}
// end sanity checks

// deploy and init the timelock
timelock = deployTimelock(ProxyAdmin(_parentChainProxyAdmin));
timelock.initialize(
_minTimelockDelay, new address[](0), _inbox, _governanceChainCoreTimelock
);
// anyone can execute
timelock.grantRole(timelock.EXECUTOR_ROLE(), address(0));

// revoke admin rights and give them to the upgrade executor
timelock.grantRole(timelock.TIMELOCK_ADMIN_ROLE(), address(_parentChainUpExec));
timelock.revokeRole(timelock.TIMELOCK_ADMIN_ROLE(), address(timelock));
timelock.revokeRole(timelock.TIMELOCK_ADMIN_ROLE(), address(this));

// grant canceller role to upgrade executor; this can be used e.g. by an admin with executor affordance granted to the upgrade executor
timelock.grantRole(timelock.CANCELLER_ROLE(), address(_parentChainUpExec));

emit Deployed(timelock, _inbox);
}

function deployTimelock(ProxyAdmin _proxyAdmin)
internal
returns (L1ArbitrumTimelock timelock)
{
address logic = address(new L1ArbitrumTimelock());
TransparentUpgradeableProxy proxy =
new TransparentUpgradeableProxy(logic, address(_proxyAdmin), bytes(""));
timelock = L1ArbitrumTimelock(payable(address(proxy)));
}
}
95 changes: 95 additions & 0 deletions src/orbit-chain-governance/lib/WrappedNativeGovToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// SPDX-License-Identifier: Apache-2.0

pragma solidity 0.8.16;

import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

interface IWETH9 {
function deposit() external payable;

function withdraw(uint256 _amount) external;
}

/// @notice Contract that be used as an Orbit chain's governance token.
/// Functions as an ERC20 wrapper for the chain's native asset and and implements the IVotesUpgradeable interface.
/// Also includes parent chain token address for Gateway compatibility.
contract WrappedNativeGovToken is
IWETH9,
Initializable,
ERC20Upgradeable,
ERC20PermitUpgradeable,
ERC20VotesUpgradeable
{
constructor() {
_disableInitializers();
}

/// @notice The address of the parent chain counterpart of this token
address public l1Address;

/// @param _name Token name
/// @param _symbol Token symbol
/// @param _parentChainTokenAddress Address of the parent chain counterpart of this token
/// @param _initialSupply Initial token supply
/// @param _initialSupplyRecipient Recipient of initial token supply
function initialize(
string memory _name,
string memory _symbol,
address _parentChainTokenAddress,
uint256 _initialSupply,
address _initialSupplyRecipient
) external initializer {
l1Address = _parentChainTokenAddress;
_mint(_initialSupplyRecipient, _initialSupply);
__ERC20_init(_name, _symbol);
__ERC20Permit_init(_name);
__ERC20Votes_init();
}

function deposit() external payable override {
depositTo(msg.sender);
}

function withdraw(uint256 amount) external override {
withdrawTo(msg.sender, amount);
}

function depositTo(address account) public payable {
_mint(account, msg.value);
}

function withdrawTo(address account, uint256 amount) public {
_burn(msg.sender, amount);
(bool success,) = account.call{value: amount}("");
require(success, "FAIL_TRANSFER");
}

receive() external payable {
depositTo(msg.sender);
}

function _afterTokenTransfer(address from, address to, uint256 amount)
internal
override(ERC20Upgradeable, ERC20VotesUpgradeable)
{
super._afterTokenTransfer(from, to, amount);
}

function _mint(address to, uint256 amount)
internal
override(ERC20Upgradeable, ERC20VotesUpgradeable)
{
super._mint(to, amount);
}

function _burn(address account, uint256 amount)
internal
override(ERC20Upgradeable, ERC20VotesUpgradeable)
{
super._burn(account, amount);
}
}
23 changes: 23 additions & 0 deletions src/orbit-chain-governance/orbit-gov.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
## Orbit Chain Governance Deployment

The Orbit Chain Governance Deployment factories deploy a cross chain governance system; proposal creation and voting takes place on an Orbit chain referred to as the "governance chain," which serves to govern contracts both on the governance chain and its parent chain via cross chain messages.

This deployment is comparable to the one carried by the `L2GovernanceFactory` / `L1GovernanceFactory` contracts, except the Orbit deployment excludes certain contracts prupose-specific to the Arbitrum DAO (e.g., the DAO treasury). For info on the DAO's governance deployment, see [Arbitrum DAO Governance](../../docs/overview.md).


### Steps
0. Prior to Orbit Governance deployment, the following contracts should already be deployed:
- Governance chain:
- Governance ERC20 Token that implements the Open Zeppelin `IVotesUpgradeable` interface.
- `UpgradeExecutor`
- `ProxyAdmin`
- Parent chain:
- `UpgradeExecutor`
- `ProxyAdmin`
1. Deploy `GovernanceChainGovFactory`` and execute `deployStep1`
2. Deploy `ParentChainGovFactory` and execute `deployStep2`; requires address of timelock on deployed in `GovernanceChainGovFactory`
3. Grant `EXECUTOR_ROLE` affordance on the parent chain `UpgradeExecutor` to the governance timelock on the parent chain. Grant `EXECUTOR_ROLE` affordance on the governance chain `UpgradeExecutor` to the [address-alias](https://docs.arbitrum.io/arbos/l1-to-l2-messaging#address-aliasing) of the governance timelock on the parent chain.

Optional:
1. Additional execution affordances can be given by granting the `EXECUTOR_ROLE` to additional contracts on the `UpradeExecutor`s; e.g., a multisig that can bypass voting and delays to make emergency upgrades (a la the Arbitrum DAO Security Council).
1. Other `EXECUTOR_ROLE` affordances that were granted the UpgradeExecutor prior to deployment to facilitate the deployment process can now be removed.
Loading