Commit 6fc50a20 authored by kf's avatar kf Committed by Kelvin Fichter

feat: add ChugSplashDictator contract

parent 2a169799
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.9; pragma solidity ^0.8.9;
import { Lib_AddressManager } from "../resolver/Lib_AddressManager.sol"; import { Lib_AddressManager } from "../../libraries/resolver/Lib_AddressManager.sol";
/** /**
* @title AddressDictator * @title AddressDictator
* @dev (glory to Arstotzka) * @dev The AddressDictator (glory to Arstotzka) is a contract that allows us to safely manipulate
* many different addresses in the AddressManager without transferring ownership of the
* AddressManager to a hot wallet or hardware wallet.
*/ */
contract AddressDictator { contract AddressDictator {
/********* /*********
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import { L1ChugSplashProxy } from "../../chugsplash/L1ChugSplashProxy.sol";
import { iL1ChugSplashDeployer } from "../../chugsplash/interfaces/iL1ChugSplashDeployer.sol";
/**
* @title ChugSplashDictator
* @dev Like the AddressDictator, but specifically for the Proxy__OVM_L1StandardBridge. We're
* working on a generalized version of this but this is good enough for the moment.
*/
contract ChugSplashDictator is iL1ChugSplashDeployer {
/*************
* Variables *
*************/
bool public isUpgrading = true;
L1ChugSplashProxy public target;
address public finalOwner;
bytes32 public codeHash;
bytes32 public messengerSlotKey;
bytes32 public messengerSlotVal;
bytes32 public bridgeSlotKey;
bytes32 public bridgeSlotVal;
/***************
* Constructor *
***************/
constructor(
L1ChugSplashProxy _target,
address _finalOwner,
bytes32 _codeHash,
bytes32 _messengerSlotKey,
bytes32 _messengerSlotVal,
bytes32 _bridgeSlotKey,
bytes32 _bridgeSlotVal
) {
target = _target;
finalOwner = _finalOwner;
codeHash = _codeHash;
messengerSlotKey = _messengerSlotKey;
messengerSlotVal = _messengerSlotVal;
bridgeSlotKey = _bridgeSlotKey;
bridgeSlotVal = _bridgeSlotVal;
}
/********************
* Public Functions *
********************/
function doActions(bytes memory _code) external {
require(keccak256(_code) == codeHash, "ChugSplashDictator: Incorrect code hash.");
target.setCode(_code);
target.setStorage(messengerSlotKey, messengerSlotVal);
target.setStorage(bridgeSlotKey, bridgeSlotVal);
target.setOwner(finalOwner);
}
/**
* Transfers ownership of this contract to the finalOwner.
* Only callable by the finalOwner, which is intended to be our multisig.
* This function shouldn't be necessary, but it gives a sense of reassurance that we can
* recover if something really surprising goes wrong.
*/
function returnOwnership() external {
require(msg.sender == finalOwner, "ChugSplashDictator: only callable by finalOwner");
target.setOwner(finalOwner);
}
}
/* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types'
import { ethers } from 'ethers'
import { hexStringEquals, sleep } from '@eth-optimism/core-utils'
/* Imports: Internal */
import { predeploys } from '../src/predeploys'
import {
getContractInterface,
getContractDefinition,
} from '../src/contract-defs'
import {
getContractFromArtifact,
waitUntilTrue,
getAdvancedContract,
deployAndPostDeploy,
} from '../src/hardhat-deploy-ethers'
const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts()
// Set up a reference to the proxy as if it were the L1StandardBridge contract.
const contract = await getContractFromArtifact(
hre,
'Proxy__OVM_L1StandardBridge',
{
iface: 'L1StandardBridge',
signerOrProvider: deployer,
}
)
// Because of the `iface` parameter supplied to the deployment function above, the `contract`
// variable that we here will have the interface of the L1StandardBridge contract. However,
// we also need to interact with the contract as if it were a L1ChugSplashProxy contract so
// we instantiate a new ethers.Contract object with the same address and signer but with the
// L1ChugSplashProxy interface.
const proxy = getAdvancedContract({
hre,
contract: new ethers.Contract(
contract.address,
getContractInterface('L1ChugSplashProxy'),
contract.signer
),
})
// First we need to set the correct implementation code. We'll set the code and then check
// that the code was indeed correctly set.
const bridgeArtifact = getContractDefinition('L1StandardBridge')
const bridgeCode = bridgeArtifact.deployedBytecode
console.log(`Setting bridge code...`)
await proxy.setCode(bridgeCode)
console.log(`Confirming that bridge code is correct...`)
await waitUntilTrue(async () => {
const implementation = await proxy.callStatic.getImplementation()
return (
!hexStringEquals(implementation, ethers.constants.AddressZero) &&
hexStringEquals(
await contract.provider.getCode(implementation),
bridgeCode
)
)
})
// Next we need to set the `messenger` address by executing a setStorage operation. We'll
// check that this operation was correctly executed by calling `messenger()` and checking
// that the result matches the value we initialized.
const l1CrossDomainMessenger = await getContractFromArtifact(
hre,
'Proxy__OVM_L1CrossDomainMessenger'
)
const l1CrossDomainMessengerAddress = l1CrossDomainMessenger.address
// Critical error, should never happen.
if (
hexStringEquals(l1CrossDomainMessengerAddress, ethers.constants.AddressZero)
) {
throw new Error(`L1CrossDomainMessenger address is set to address(0)`)
}
console.log(
`Setting messenger address to ${l1CrossDomainMessengerAddress}...`
)
await proxy.setStorage(
ethers.utils.hexZeroPad('0x00', 32),
ethers.utils.hexZeroPad(l1CrossDomainMessengerAddress, 32)
)
console.log(`Confirming that messenger address was correctly set...`)
await waitUntilTrue(async () => {
return hexStringEquals(
await contract.messenger(),
l1CrossDomainMessengerAddress
)
})
// Now we set the bridge address in the same manner as the messenger address.
console.log(`Setting l2 bridge address to ${predeploys.L2StandardBridge}...`)
await proxy.setStorage(
ethers.utils.hexZeroPad('0x01', 32),
ethers.utils.hexZeroPad(predeploys.L2StandardBridge, 32)
)
console.log(`Confirming that l2 bridge address was correctly set...`)
await waitUntilTrue(async () => {
return hexStringEquals(
await contract.l2TokenBridge(),
predeploys.L2StandardBridge
)
})
// Finally we transfer ownership of the proxy to the ovmAddressManagerOwner address.
const owner = (hre as any).deployConfig.ovmAddressManagerOwner
console.log(`Setting owner address to ${owner}...`)
await proxy.setOwner(owner)
console.log(`Confirming that owner address was correctly set...`)
await waitUntilTrue(async () => {
return hexStringEquals(
await proxy.connect(proxy.signer.provider).callStatic.getOwner({
from: ethers.constants.AddressZero,
}),
owner
)
})
// Deploy a copy of the implementation so it can be successfully verified on Etherscan.
console.log(`Deploying a copy of the bridge for Etherscan verification...`)
await deployAndPostDeploy({
hre,
name: 'L1StandardBridge_for_verification_only',
contract: 'L1StandardBridge',
args: [],
})
}
deployFn.tags = ['L1StandardBridge', 'upgrade']
export default deployFn
/* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types'
import { ethers } from 'ethers'
/* Imports: Internal */
import { predeploys } from '../src/predeploys'
import { getContractDefinition } from '../src/contract-defs'
import {
getContractFromArtifact,
deployAndPostDeploy,
} from '../src/hardhat-deploy-ethers'
const deployFn: DeployFunction = async (hre) => {
const Proxy__OVM_L1StandardBridge = await getContractFromArtifact(
hre,
'Proxy__OVM_L1StandardBridge'
)
// Note: if the contract being deployed has immutable values this approach would not work.
const bridgeArtifact = getContractDefinition('L1StandardBridge')
const bridgeCode = bridgeArtifact.deployedBytecode
const Proxy__OVM_L1CrossDomainMessenger = await getContractFromArtifact(
hre,
'Proxy__OVM_L1CrossDomainMessenger'
)
await deployAndPostDeploy({
hre,
name: 'ChugSplashDictator',
contract: 'ChugSplashDictator',
args: [
Proxy__OVM_L1StandardBridge.address,
(hre as any).deployConfig.ovmAddressManagerOwner,
ethers.utils.keccak256(bridgeCode),
ethers.utils.hexZeroPad('0x00', 32),
ethers.utils.hexZeroPad(Proxy__OVM_L1CrossDomainMessenger.address, 32),
ethers.utils.hexZeroPad('0x01', 32),
ethers.utils.hexZeroPad(predeploys.L2StandardBridge, 32),
],
})
}
deployFn.tags = ['upgrade', 'ChugSplashDictator']
export default deployFn
/* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types'
import { ethers } from 'ethers'
import { hexStringEquals } from '@eth-optimism/core-utils'
/* Imports: Internal */
import { getContractDefinition } from '../src/contract-defs'
import {
getContractFromArtifact,
waitUntilTrue,
deployAndPostDeploy,
} from '../src/hardhat-deploy-ethers'
const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts()
const ChugSplashDictator = await getContractFromArtifact(
hre,
'ChugSplashDictator',
{
signerOrProvider: deployer,
}
)
const Proxy__OVM_L1StandardBridge = await getContractFromArtifact(
hre,
'Proxy__OVM_L1StandardBridge',
{
iface: 'L1ChugSplashProxy',
signerOrProvider: deployer,
}
)
// Make sure the dictator has been initialized with the correct bridge code.
const bridgeArtifact = getContractDefinition('L1StandardBridge')
const bridgeCode = bridgeArtifact.deployedBytecode
const codeHash = await ChugSplashDictator.codeHash()
if (ethers.utils.keccak256(bridgeCode) !== codeHash) {
throw new Error('code hash does not match actual bridge code')
}
const currentOwner = await Proxy__OVM_L1StandardBridge.connect(
Proxy__OVM_L1StandardBridge.signer.provider
).callStatic.getOwner({
from: ethers.constants.AddressZero,
})
const finalOwner = await ChugSplashDictator.finalOwner()
// Check if the hardhat runtime environment has the owner of the proxy. This will only
// happen in CI. If this is the case, we can skip directly to transferring ownership over to the
// ChugSplashDictator contract.
const hreSigners = await hre.ethers.getSigners()
const hreHasOwner = hreSigners.some((signer) => {
return hexStringEquals(signer.address, currentOwner)
})
if (hreHasOwner) {
// Hardhat has the owner loaded into it, we can skip directly to transferOwnership.
const owner = await hre.ethers.getSigner(currentOwner)
await Proxy__OVM_L1StandardBridge.connect(owner).setOwner(
ChugSplashDictator.address
)
} else {
const messengerSlotKey = await ChugSplashDictator.messengerSlotKey()
const messengerSlotVal = await ChugSplashDictator.messengerSlotVal()
const bridgeSlotKey = await ChugSplashDictator.bridgeSlotKey()
const bridgeSlotVal = await ChugSplashDictator.bridgeSlotVal()
console.log(`
The ChugSplashDictator contract (glory to Arstotzka) has been deployed.
FOLLOW THESE INSTRUCTIONS CAREFULLY!
(1) Review the storage key/value pairs below and make sure they match the expected values:
${messengerSlotKey}: ${messengerSlotVal}
${bridgeSlotKey}: ${bridgeSlotVal}
(2) Review the CURRENT and FINAL proxy owners and verify that these are the expected values:
Current proxy owner: (${currentOwner})
Final proxy owner: (${finalOwner})
[${
currentOwner === finalOwner
? 'THESE ARE THE SAME ADDRESSES'
: 'THESE ARE >>>NOT<<< THE SAME ADDRESSES'
}]
(3) Transfer ownership of the L1ChugSplashProxy located at (${
Proxy__OVM_L1StandardBridge.address
})
to the ChugSplashDictator contract located at the following address:
TRANSFER OWNERSHIP TO THE FOLLOWING ADDRESS ONLY:
>>>>> (${ChugSplashDictator.address}) <<<<<
(4) Wait for the deploy process to continue.
`)
}
// Wait for ownership to be transferred to the AddressDictator contract.
await waitUntilTrue(
async () => {
return hexStringEquals(
await Proxy__OVM_L1StandardBridge.connect(
Proxy__OVM_L1StandardBridge.signer.provider
).callStatic.getOwner({
from: ethers.constants.AddressZero,
}),
ChugSplashDictator.address
)
},
{
// Try every 30 seconds for 500 minutes.
delay: 30_000,
retries: 1000,
}
)
// Set the addresses!
console.log('Ownership successfully transferred. Invoking doActions...')
await ChugSplashDictator.doActions(bridgeCode)
console.log(`Confirming that owner address was correctly set...`)
await waitUntilTrue(async () => {
return hexStringEquals(
await Proxy__OVM_L1StandardBridge.connect(
Proxy__OVM_L1StandardBridge.signer.provider
).callStatic.getOwner({
from: ethers.constants.AddressZero,
}),
finalOwner
)
})
// Deploy a copy of the implementation so it can be successfully verified on Etherscan.
console.log(`Deploying a copy of the bridge for Etherscan verification...`)
await deployAndPostDeploy({
hre,
name: 'L1StandardBridge_for_verification_only',
contract: 'L1StandardBridge',
args: [],
})
}
deployFn.tags = ['L1StandardBridge', 'upgrade']
export default deployFn
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment